Skip to content

Conversation

@stefrado
Copy link
Contributor

@stefrado stefrado commented Nov 24, 2025

Summary

This PR refactors the usage of React Preact, Web components and Storybook.

With these changes we can easily create web components.

This PR refactored the following components:

  • material-icon (not yet used as web component).
  • side-navigation
  • kvk-branch-selector
  • action-list
  • oip-homepage-plugin-section
  • oip-homepage-plugin-card (this includes the required changes for balie afspraken)
  • oip-loading-spinner

Example rendering

Django template examples

{{ data|json_script:"id" }}
<action-list actions-id="id"></action-list>
<oip-homepage-plugin-section>
  <oip-homepage-plugin-card><oip-homepage-plugin-card/>
  <oip-homepage-plugin-card><oip-homepage-plugin-card/>
  <oip-homepage-plugin-card><oip-homepage-plugin-card/>
</oip-homepage-plugin-section>

Also this PR pins Node in the CI to 20.

Issue References

Multiple:

  • Taiga User Story:
  • Taiga Issue:
  • Taiga Dimpact Issue:
  • Github Issue:

Checklist

  • CHANGELOG.rst updated
    • Deployment notes added
    • Breaking changes flagged
  • Documentation updated
  • Codecov report reviewed for untested lines
  • Storybook component updated/created
  • Vitest tests added/updated
  • Updated the docker-compose.yml environment variables

@stefrado stefrado changed the title Exp/stencil web components Exp/preact web components Nov 24, 2025
@stefrado stefrado force-pushed the exp/stencil-web-components branch from 9a06a7a to 2a2d9d3 Compare November 27, 2025 12:45

// Normalize to MenuItem[][] - check if first element is an array
const normalized =
rawData.length > 0 && Array.isArray(rawData[0])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

so it is always at least 1 item, but what if What if rawData is empty? (not sure if we have that situation)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is a layer to work with the actual accepted props for the MenuItems.

With this we accept their standard MenuItem[][], this renders mutliple lists with spacing.

@codecov-commenter
Copy link

codecov-commenter commented Nov 27, 2025

Codecov Report

❌ Patch coverage is 94.54545% with 3 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.65%. Comparing base (9ac56d8) to head (d229942).

Files with missing lines Patch % Lines
src/open_inwoner/cms/plugins/views.py 57.14% 3 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           develop    #2043      +/-   ##
===========================================
- Coverage    92.65%   92.65%   -0.01%     
===========================================
  Files         1222     1222              
  Lines        46832    46854      +22     
===========================================
+ Hits         43392    43411      +19     
- Misses        3440     3443       +3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copy link
Collaborator

@swrichards swrichards left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some preliminary comments, let me know if you think we should discuss. Otherwise good work here.

@stefrado stefrado force-pushed the exp/stencil-web-components branch from f1f9fc4 to 20a1b74 Compare December 1, 2025 15:42
@stefrado stefrado force-pushed the exp/stencil-web-components branch 2 times, most recently from aa95568 to d3e742b Compare December 2, 2025 16:05
@stefrado stefrado requested a review from swrichards December 4, 2025 10:39
@stefrado stefrado force-pushed the exp/stencil-web-components branch 3 times, most recently from 9efc12c to d4efc2f Compare December 8, 2025 15:21
@stefrado stefrado marked this pull request as ready for review December 8, 2025 16:36
@stefrado stefrado force-pushed the exp/stencil-web-components branch from 8a60764 to d0bb07e Compare December 8, 2025 16:36
Copy link
Contributor

@jiromaykin jiromaykin left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Almost ready to merge :-)

children?: ComponentChildren;
}>;

const HomepagePluginSection: FC<IHomepagePluginSectionProps> = ({
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A general question, also to @swrichards : this is a very rudimentary component that either exists or doesn't so do we not need front-end spec tests for these?

Are we going to work in such a way most testing is done by the back-end for these type of simple components?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not write a test for this component yet (time), but in my opinion every should this component have a test.


# test item
items = pyquery.find(".card-container .card")
items = pyquery.find("oip-homepage-plugin-card")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggestion: not about to this test file, but adding to the _/cms/plugins/cms_plugins/appointments.py file: I would love to have a proper mock for the front-end .

If I copy the text from the Figma design and use this code, I think it could be better @swrichards ?:

from django.utils.translation import gettext as _

import structlog
from cms.plugin_base import CMSPluginBase
from cms.plugin_pool import plugin_pool

from open_inwoner.cms.plugins.models.appointments import UserAppointments
from open_inwoner.qmatic.client import NoServiceConfigured, qmatic_client_factory

logger = structlog.stdlib.get_logger(__name__)


@plugin_pool.register_plugin
class UserAppointmentsPlugin(CMSPluginBase):
    model = UserAppointments
    module = _("General")
    name = _("My appointments")
    render_template = "cms/plugins/appointments/appointments.html"

    def render(self, context, instance, placeholder):
        request = context["request"]
        if not request.user.is_authenticated or not request.user.has_verified_email():
            appointments = []
        else:
            try:
                client = qmatic_client_factory()
            except NoServiceConfigured:
                logger.exception("Error occurred while creating Qmatic client")
                appointments = []
            else:
                appointments = client.list_appointments_for_customer(
                    request.user.verified_email
                )

        # MOCK DATA
        if not appointments:
            from datetime import datetime

            class MockBranch:
                timeZone = "Europe/Amsterdam"

            class MockAppointment:
                def __init__(self, service_name, date_str, address_line, city, public_id):
                    self.title = service_name
                    self.start = datetime.fromisoformat(date_str)
                    self.services = [type('Service', (), {'name': service_name})()]
                    self.branch = type('Branch', (), {
                        'timeZone': "Europe/Amsterdam",
                        'addressLine2': address_line,
                        'addressCity': city,
                    })()
                    self.publicId = public_id

            appointments = [
                MockAppointment("rijbewijs ophalen", "2025-09-21T13:15:00+02:00", "Raadhuisplein 10", "Haren", "rijbewijs-001"),
                MockAppointment("VOG aanvragen", "2025-09-30T15:45:00+02:00", "Kreupelstraat 1", "Groningen", "vog-001"),
            ]

        context.update(
            {
                "instance": instance,
                "appointments": appointments,
            }
        )
        return context


Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I used this to mock the appointments:

import factory

from open_inwoner.qmatic.tests.factories import (
    AppointmentFactory,
    BranchDetailFactory,
    QmaticServiceFactory,
)

appointments_mocks = [
    AppointmentFactory.build(
        title=factory.Faker("catch_phrase").evaluate(None, None, {"locale": "nl_NL"}),
        start=factory.Faker(
            "date_time_between", start_date="now", end_date="+30d"
        ).evaluate(None, None, {"locale": "nl_NL"}),
        notes=factory.Faker("text", max_nb_chars=120).evaluate(
            None, None, {"locale": "nl_NL"}
        ),
        branch=BranchDetailFactory.build(
            name=factory.Faker("company").evaluate(None, None, {"locale": "nl_NL"}),
            timeZone=factory.Faker("timezone").evaluate(
                None, None, {"locale": "nl_NL"}
            ),
            addressCity=factory.Faker("city").evaluate(None, None, {"locale": "nl_NL"}),
            addressLine1=factory.Faker("street_name").evaluate(
                None, None, {"locale": "nl_NL"}
            ),
            addressLine2=factory.Faker("street_address").evaluate(
                None, None, {"locale": "nl_NL"}
            ),
            addressZip=factory.Faker("postcode").evaluate(
                None, None, {"locale": "nl_NL"}
            ),
        ),
        services=[
            QmaticServiceFactory.build(
                name=factory.Faker("sentence", nb_words=2).evaluate(
                    None, None, {"locale": "nl_NL"}
                )
            )
        ],
    )
    for i in range(0, 10)
]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants