Domain Project Overview

Learn what is the Domain Project

Domain Project

The Domain Project is the place where BlazorForKids sets up everything needed to build the database and handle backend logic. Here, it generates the entities, models, and Mediator-style commands and queries that power the Web Project. Now, let’s take a closer look at the generated types for a sample entity called Employee.

Entity Creation

To create an entity, developers can choose the standard approach or use the BlazorForKids method. The BlazorForKids method involves creating a partial class that inherits from IBKEntity<TEntity, TDbContext>. This approach requires implementing a method that the Source Generators use to automatically generate the necessary code.

Here's an example of how to define an entity using the BlazorForKids approach:

//this is a sample entitypublicpartialclassEmployee:IBkEntity<Employee,ApplicationDbContext>

{

    publicvoidBkConfiguration(IBkEntityDesigner<Employee,ApplicationDbContext>designer)

    {

//add here properties    }

}

Once you create a partial class and implement IBkEntity, the Source Generators already prepare several components for you. These include: another partial class where properties and methods will be added as you define settings, an EntityModel that is automatically updated with the necessary members for CRUD operations, and a full set of Mediator-style commands and queries like:

  • EmployeeCreateRequest
  • EmployeeUpdateRequest
  • EmployeeDeleteRequest
  • EmployeeQuery
  • EmployeeDefaultCatalogQuery
  • EmployeeFirstOrDefaultQuery
  • EmployeeGetByIdQuery

Now we’ll take a look at the generated types. Keep in mind that this is just a snapshot of the initial state—no properties have been defined yet. Once you begin adding properties or settings to your entity, everything will update dynamically. You’ll see the full experience in action as you start using Blazor For Kids.

  • EmployeeId - A readonly record struct based on Guid, used as the entity's unique identifier.
  • publicreadonlyrecordstructEmployeeId(Guidvalue):IBkEntityId<EmployeeId,EmployeeModel,Employee,ApplicationDbContext>,IComparable<EmployeeId>

    {

        publicGuidValue{get;init;}=value;

        publicstaticEmployeeIdEmpty=>new(Guid.Empty);

        publicstaticEmployeeIdNewId=>new(Guid.CreateVersion7());

        publicstaticboolTryParse(string?value,outEmployeeIdemployeeId)

        {

            if(!string.IsNullOrWhiteSpace(value)&&Guid.TryParse(value,outvarparsedGuid))

            {

                employeeId=newEmployeeId(parsedGuid);

                returntrue;

            }

            employeeId=Empty;

            returnfalse;

        }

        publicstaticEmployeeIdParseOrEmpty(string?employeeId)

        {

            if(TryParse(employeeId,outvarId))

            {

                returnId;

            }

            returnEmployeeId.Empty;

        }

        publicintCompareTo(EmployeeIdother)

        {

            returnValue.CompareTo(other.Value);

        }

        publicoverridestringToString()=>Value.ToString();

    }

  • Employee - A partial class with an Id property of type EmployeeId.
  • publicpartialclassEmployee:IEmployeeEditModel,IBkEntityKey<EmployeeId>,IBkEditModel<EmployeeModel>,IBkEntityDefaultOrderBy<Employee>

    {

        [Key]

        publicEmployeeIdId{get;privateset;}

        publicstaticEmployeeCreate()

        {

            returnnewEmployee

            {

                Id=EmployeeId.NewId

            };

        }

        publicvoidUpdate()

        {

        }

        publicBkValidationResultIsValid(IServiceProvider?serviceProvider=null)

        {

            returnBkValidationResult.CreateValidationResult(this,serviceProvider);

        }

        publicstaticEmployeeMapFrom(IEmployeeEditModelitem)

        {

            returnnewEmployee

            {

                Id=item.Id

            };

        }

        publicstaticExpression<Func<Employee,BkCatalogItem>>ProjectToDefaultCatalog=>entity=>newBkCatalogItem(entity.Id);

        publicstaticExpression<Func<Employee,object>>DefaultOrderBy=>a=>a.Id;

        publicclassConfiguration:IEntityTypeConfiguration<Employee>

        {

            publicvoidConfigure(EntityTypeBuilder<Employee>builder)

            {

                builder.HasKey(bk=>bk.Id);

                builder.Property(bk=>bk.Id).HasConversion(bk=>bk.Value,bk=>newEmployeeId(bk)).ValueGeneratedNever();

            }

        }

    }

  • EntityModel - Used for CRUD operations related to the entity.
  • publicclassEmployeeModel:IEmployeeEditModel,IBkCloneable<Entities.EmployeeModel>,IBkEntityDefaultOrderBy<Entities.EmployeeModel>,IBkEntityKey<EmployeeId>,IBkProjectFromEntity<Entities.EmployeeModel,Employee,ApplicationDbContext>,IBkEntityModel<Employee,ApplicationDbContext>,IBkEntityDto<Employee>

    {

        [Key]

        publicEmployeeIdId{get;set;}

        publicEmployeeCreate()

        {

            varentity=Employee.Create();

            Id=entity.Id;

            returnentity;

        }

        publicvoidUpdate(Employeeemployee)

        {

            employee.Update();

        }

        publicEntities.EmployeeModelClone()

        {

            returnnewEntities.EmployeeModel

            {

                Id=Id

            };

        }

        publicBkValidationResultIsValid(IServiceProvider?serviceProvider=null)

        {

            returnBkValidationResult.CreateValidationResult(this,serviceProvider);

        }

        publicstaticExpression<Func<Employee,Entities.EmployeeModel>>ProjectFromEntity=>entity=>newEntities.EmployeeModel

        {

            Id=entity.Id

        };

        publicstaticExpression<Func<Entities.EmployeeModel,object>>DefaultOrderBy=>a=>a.Id;

        publicEmployeeMapToEntity()

        {

            returnEmployee.MapFrom(this);

        }

    }

Now that the initial setup is complete, you're ready to start adding properties. From here on, I’ll let you explore and discover how the framework works as you build. It’s designed to be intuitive and reveal its features step by step.

Commands and Queries

In addition to entity creation and management, the Blazor For Kids framework automates the generation of commands and queries related to each entity. These are essential for implementing the business logic associated with entity operations.

Generated Commands


🟢 EmployeeCreateRequest

This command is used to create a new employee in the system.

  • It takes the data from a model and builds a new Employee object.
  • It checks if everything is valid (for example, required fields are filled).
  • If valid, it saves the new employee to the database.

publicclassEmployeeCreateRequest(EmployeeModeleditModel):IBkRequest<IBkCommandResult>,IBkCreateRequest

{

    publicEmployeeModelEmployeeModel{get;}=editModel;

    publicclassHandler(IDbContextFactory<ApplicationDbContext>dbContextFactory,IServiceProviderserviceProvider,IBkEventPublisher<EmployeeCreated>eventAggregator):IBkRequestHandler<EmployeeCreateRequest,IBkCommandResult>

    {

        publicasyncTask<IBkCommandResult>Handle(EmployeeCreateRequestrequest,CancellationTokencancellationToken)

        {

            awaitusingvarcontext=awaitdbContextFactory.CreateDbContextAsync(cancellationToken);

            varemployee=request.EmployeeModel.Create();

            varisValid=employee.IsValid(serviceProvider);

            if(isValid==false)

                returnisValid.CommandResult();

            awaitcontext.Employee.AddAsync(employee,cancellationToken);

            returnawaitcontext.ApplyChangesWithResultAsync(cancellationToken);

        }

    }

}


🟡 EmployeeUpdateRequest

This command is used to update an existing employee.

  • It finds the existing employee in the database using their ID.
  • If the employee exists, it updates their details using the data from the model.
  • Then it checks if the updated employee is still valid.
  • If valid, it saves the changes to the database.

publicclassEmployeeUpdateRequest(EmployeeModeleditModel):IBkRequest<IBkCommandResult>,IBkUpdateRequest

{

    publicEmployeeModelEmployeeModel{get;}=editModel;

    publicclassHandler(IDbContextFactory<ApplicationDbContext>dbContextFactory,IServiceProviderserviceProvider,IBkEventPublisher<EmployeeUpdated>eventAggregator):IBkRequestHandler<EmployeeUpdateRequest,IBkCommandResult>

    {

        publicasyncTask<IBkCommandResult>Handle(EmployeeUpdateRequestrequest,CancellationTokencancellationToken)

        {

            awaitusingvarcontext=awaitdbContextFactory.CreateDbContextAsync(cancellationToken);

            varemployee=awaitcontext.Employee.FindAsync([request.EmployeeModel.Id],cancellationToken:cancellationToken);

            if(employeeisnull)

                returnBkCommandResult.Warning();

            request.EmployeeModel.Update(employee);

            varisValid=employee.IsValid(serviceProvider);

            if(isValid==false)

                returnisValid.CommandResult();

            returnawaitcontext.ApplyChangesWithResultAsync(cancellationToken);

        }

    }

}


🔴 EmployeeDeleteRequest

This command is used to remove an employee from the system.

  • It tries to find the employee using their ID.
  • If found, it deletes them from the database.

publicclassEmployeeDeleteRequest(EmployeeModeleditModel):IBkRequest<IBkCommandResult>,IBkDeleteRequest

{

    publicEmployeeModelEmployeeModel{get;}=editModel;

    publicclassHandler(IDbContextFactory<ApplicationDbContext>dbContextFactory,IBkEventPublisher<EmployeeDeleted>eventAggregator):IBkRequestHandler<EmployeeDeleteRequest,IBkCommandResult>

    {

        publicasyncTask<IBkCommandResult>Handle(EmployeeDeleteRequestrequest,CancellationTokencancellationToken)

        {

            awaitusingvarcontext=awaitdbContextFactory.CreateDbContextAsync(cancellationToken);

            varemployee=awaitcontext.Employee.FindAsync([request.EmployeeModel.Id],cancellationToken:cancellationToken);

            if(employeeisnull)

                returnBkCommandResult.Warning();

            context.Employee.Remove(employee);

            varresult=awaitcontext.ApplyChangesWithResultAsync(cancellationToken);

            if(result.Type==BkResultType.Error)

            {

                returnresult;

            }

            returnawaiteventAggregator.Publish(newEmployeeDeleted(request.EmployeeModel),cancellationToken);

        }

    }

}

These commands follow the Mediator pattern, where each request is sent through a central handler instead of calling services directly. This helps keep responsibilities separated, making the code easier to read, test, and maintain. Each handler knows exactly how to deal with its request, while the rest of the system stays loosely coupled.

Generated Queries

Main Query Request

This query is used to get a paginated list of employees, based on filters or a given page size. It’s handy when you want to display a list of employees with sorting, filtering, and paging already built in.

publicclassEmployeeQuery:IBkRequest<BkPaginationQueryResult<EmployeeModel>>,IBkQueryRequest

{

    publicstringCacheKey{get;}

    privateBkFilterCollection<Employee>Filters{get;}

    publicEmployeeQuery(BkFilterCollection<Employee>filters)

    {

        CacheKey=$"Employee_Query_{filters.CacheKey()}";

        Filters=filters;

    }

    publicEmployeeQuery(int?pageSize=null)

    {

        varfilters=newBkFilterCollection<Employee>();

        filters.Paginator.SetPageSize(pageSize??0);

        CacheKey=$"Employee_Query_{pageSize}";

        Filters=filters;

    }

    publicclassHandler(IDbContextFactory<ApplicationDbContext>dbContextFactory):IBkRequestHandler<EmployeeQuery,BkPaginationQueryResult<EmployeeModel>>

    {

        publicasyncTask<BkPaginationQueryResult<EmployeeModel>>Handle(EmployeeQueryrequest,CancellationTokencancellationToken)

        {

            awaitusingvarcontext=awaitdbContextFactory.CreateDbContextAsync(cancellationToken);

            returnawaitcontext.Employee.ToPaginationResult(EmployeeModel.ProjectFromEntity,request.Filters,cancellationToken);

        }

    }

}

Catalog Query

This one is used to get a lightweight version of the employee list, ideal for dropdowns or selection boxes. It runs with optional filters and only fetches the minimal data needed for catalogs.

publicclassEmployeeDefaultCatalogQuery(ImmutableArray<Expression<Func<Employee,bool>>>?filters=null):IBkRequest<IBkCatalog>,IBkQueryRequest

{

    publicstringCacheKey{get;}=$"Employee_DefaultCatalog_{filters.CacheKey()}";

    privateImmutableArray<Expression<Func<Employee,bool>>>Filters{get;}=filters??[];

    publicclassHandler(IDbContextFactory<ApplicationDbContext>dbContextFactory):IBkRequestHandler<EmployeeDefaultCatalogQuery,IBkCatalog>

    {

        publicasyncTask<IBkCatalog>Handle(EmployeeDefaultCatalogQueryrequest,CancellationTokencancellationToken)

        {

            awaitusingvarcontext=awaitdbContextFactory.CreateDbContextAsync(cancellationToken);

            IQueryable<Employee>entities=context.Employee.AsNoTracking();

            foreach(varfilterinrequest.Filters)

            {

                entities=entities.Where(filter);

            }

            varquery=entities.Select(Employee.ProjectToDefaultCatalog);

            varitems=awaitquery.ToListAsync(cancellationToken);

            returnnewBkCatalog(items);

        }

    }

}

First Or Default

This query fetches the first employee that matches a condition. If no match is found, it returns nothing. It’s useful when you expect just one result or want to check for existence.

publicclassEmployeeFirstOrDefaultQuery(Expression<Func<Employee,bool>>predicate):IBkRequest<EmployeeModel?>,IBkQueryRequest

{

    publicstringCacheKey{get;}=$"Employee_FirstOrDefaultQuery_{predicate.CacheKey()}";

    privateExpression<Func<Employee,bool>>Predicate{get;}=predicate;

    publicclassHandler(IDbContextFactory<ApplicationDbContext>dbContextFactory):IBkRequestHandler<EmployeeFirstOrDefaultQuery,EmployeeModel?>

    {

        publicasyncTask<EmployeeModel?>Handle(EmployeeFirstOrDefaultQueryrequest,CancellationTokencancellationToken)

        {

            awaitusingvarcontext=awaitdbContextFactory.CreateDbContextAsync(cancellationToken);

            returnawaitcontext.Employee.AsNoTracking().AsSplitQuery().Where(request.Predicate).Select(EmployeeModel.ProjectFromEntity).FirstOrDefaultAsync(cancellationToken);

        }

    }

}

Get By Id

This query is used to retrieve the details of a single employee using their unique identifier. It’s the simplest way to fetch one item when you already know its ID.

publicclassEmployeeGetByIdQuery:IBkRequest<EmployeeModel?>,IBkQueryRequest

{

    publicstringCacheKey{get;}

    privatereadonlyEmployeeIdId;

    publicEmployeeGetByIdQuery(stringid)

    {

        Id=EmployeeId.ParseOrEmpty(id);

        CacheKey=$"Employee_QueryById_{id}";

    }

    publicEmployeeGetByIdQuery(EmployeeIdid)

    {

        Id=id;

        CacheKey=$"Employee_QueryById_{id}";

    }

    publicclassHandler(IDbContextFactory<ApplicationDbContext>dbContextFactory):IBkRequestHandler<EmployeeGetByIdQuery,EmployeeModel?>

    {

        publicasyncTask<EmployeeModel?>Handle(EmployeeGetByIdQueryrequest,CancellationTokencancellationToken)

        {

            awaitusingvarcontext=awaitdbContextFactory.CreateDbContextAsync(cancellationToken);

            varitem=awaitcontext.Employee.AsNoTracking().Where(a=>a.Id==request.Id).Select(EmployeeModel.ProjectFromEntity).FirstOrDefaultAsync(cancellationToken);

            returnitem;

        }

    }

}

Naming Conventions in the Domain Project

The Domain Project follows a consistent and intuitive naming convention to clearly identify the role and purpose of each type generated for your entity.

  • EmployeeCreateRequest, EmployeeUpdateRequest, EmployeeDeleteRequest — represent the command objects used to handle Create, Update, and Delete operations. The suffix Request indicates that these are Mediator-style command requests.
  • EmployeeQuery, EmployeeDefaultCatalogQuery, EmployeeFirstOrDefaultQuery, EmployeeGetByIdQuery — represent different types of read operations. The suffix Query clearly indicates their role in retrieving data based on various conditions.

Each of these types is prefixed with the name of the associated entity (Employee) to maintain clarity and grouping across the domain. This ensures that all related logic for an entity remains easily identifiable and organized.

The EmployeeModel is a mutable type designed to facilitate user interaction, particularly in forms and UI bindings. Since the core Employee entity is immutable (its properties have private setters), it cannot be directly bound to form inputs in the UI. The EmployeeModel bridges this gap by serving as the editable representation of the entity and is used throughout the CRUD flow in the application.

Automatic Registration of Entities in ApplicationDbContext

The ApplicationDbContext must be manually defined in the Domain project. Typically, this is already provided in the project template. It's important that this class is marked as partial, as this allows the Source Generator to extend it with another partial class that registers all the entities created by the BlazorForKids framework. Additionally, having direct access to ApplicationDbContext enables developers to write custom configurations, methods, or register other entities not managed by BlazorForKids, offering full flexibility when needed.

publicpartialclassApplicationDbContext(DbContextOptions<ApplicationDbContext>options):IdentityDbContext<ApplicationUser>(options)

{

    protectedoverridevoidOnConfiguring(DbContextOptionsBuilderoptionsBuilder)

    {

// Add any custom configuration here        base.OnConfiguring(optionsBuilder);

    }

    protectedoverridevoidOnModelCreating(ModelBuildermodelBuilder)

    {

        base.OnModelCreating(modelBuilder);

        modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);

    }

}

When using BlazorForKids to define your entities, the framework offers helpful automation that simplifies your setup process. Specifically, every entity you define is automatically registered in the ApplicationDbContext.

publicpartialclassApplicationDbContext

{

    publicDbSet<Department>Department{get;set;}=default!;

    publicDbSet<Employee>Employee{get;set;}=default!;

}

In addition to registration, the framework also creates the necessary entity configurations for you (see Configuration class inside the generated Entity). This means you don't need to manually add DbSet declarations or implement IEntityTypeConfiguration classes for each entity — BlazorForKids takes care of this, ensuring a clean and consistent setup across your application.

This automation allows you to focus on defining your domain logic, while the framework handles the boilerplate setup in the background.

Centralized Access to Generated Commands and Queries

One of the standout advantages of using BlazorForKids is the automatic generation of Mediator-style commands and queries. While this feature saves significant development time, it can become challenging to keep track of all the generated types, especially as your project grows.

To address this, the framework provides a central interface named IBkMediatorDomain. This interface serves as a single entry point that exposes all the generated commands and queries for your entities. Each method represents a specific action like creating, updating, querying, or deleting records.

publicpartialinterfaceIBkMediatorDomain

{

    Task<IBkCommandResult>EmployeeCreateRequest(EmployeeModeleditModel,CancellationTokencancellationToken=default);

    Task<IBkCommandResult>EmployeeUpdateRequest(EmployeeModeleditModel,CancellationTokencancellationToken=default);

    Task<IBkCommandResult>EmployeeDeleteRequest(EmployeeModeleditModel,CancellationTokencancellationToken=default);

    Task<IBkCatalog>EmployeeDefaultCatalogQuery(ImmutableArray<Expression<Func<Employee,bool>>>?filters=null,CancellationTokencancellationToken=default);

    Task<EmployeeModel?>EmployeeFirstOrDefaultQuery(Expression<Func<Employee,bool>>predicate,CancellationTokencancellationToken=default);

    Task<EmployeeModel?>EmployeeGetByIdQuery(stringid,CancellationTokencancellationToken=default);

    Task<EmployeeModel?>EmployeeGetByIdQuery(EmployeeIdid,CancellationTokencancellationToken=default);

    Task<BkPaginationQueryResult<EmployeeModel>>EmployeeQuery(BkFilterCollection<Employee>filters,CancellationTokencancellationToken=default);

    Task<BkPaginationQueryResult<EmployeeModel>>EmployeeQuery(int?pageSize=null,CancellationTokencancellationToken=default);

    Task<BkPaginationQueryResult<BkAdminModel>>QueryApplicationUsers(BkFilterCollection<ApplicationUser>filters,CancellationTokencancellationToken=default);

}

An accompanying implementation class, BkMediatorDomain, wires up each method through the internal request dispatcher. This class is automatically registered with the Dependency Injection system, allowing you to easily inject IBkMediatorDomain wherever you need to perform data operations in your application.

publicpartialclassBkMediatorDomain(IBkRequestDispatcherdispatcher):IBkMediatorDomain

{

    publicTask<IBkCommandResult>EmployeeCreateRequest(EmployeeModeleditModel,CancellationTokencancellationToken=default)

    {

        returndispatcher.Send<EmployeeCreateRequest,IBkCommandResult>(newEmployeeCreateRequest(editModel),cancellationToken);

    }

    publicTask<IBkCommandResult>EmployeeUpdateRequest(EmployeeModeleditModel,CancellationTokencancellationToken=default)

    {

        returndispatcher.Send<EmployeeUpdateRequest,IBkCommandResult>(newEmployeeUpdateRequest(editModel),cancellationToken);

    }

    publicTask<IBkCommandResult>EmployeeDeleteRequest(EmployeeModeleditModel,CancellationTokencancellationToken=default)

    {

        returndispatcher.Send<EmployeeDeleteRequest,IBkCommandResult>(newEmployeeDeleteRequest(editModel),cancellationToken);

    }

    publicTask<IBkCatalog>EmployeeDefaultCatalogQuery(ImmutableArray<Expression<Func<Employee,bool>>>?filters=null,CancellationTokencancellationToken=default)

    {

        returndispatcher.Send<EmployeeDefaultCatalogQuery,IBkCatalog>(newEmployeeDefaultCatalogQuery(filters),cancellationToken);

    }

    publicTask<EmployeeModel?>EmployeeFirstOrDefaultQuery(Expression<Func<Employee,bool>>predicate,CancellationTokencancellationToken=default)

    {

        returndispatcher.Send<EmployeeFirstOrDefaultQuery,EmployeeModel?>(newEmployeeFirstOrDefaultQuery(predicate),cancellationToken);

    }

    publicTask<EmployeeModel?>EmployeeGetByIdQuery(stringid,CancellationTokencancellationToken=default)

    {

        returndispatcher.Send<EmployeeGetByIdQuery,EmployeeModel?>(newEmployeeGetByIdQuery(id),cancellationToken);

    }

    publicTask<EmployeeModel?>EmployeeGetByIdQuery(EmployeeIdid,CancellationTokencancellationToken=default)

    {

        returndispatcher.Send<EmployeeGetByIdQuery,EmployeeModel?>(newEmployeeGetByIdQuery(id),cancellationToken);

    }

    publicTask<BkPaginationQueryResult<EmployeeModel>>EmployeeQuery(BkFilterCollection<Employee>filters,CancellationTokencancellationToken=default)

    {

        returndispatcher.Send<EmployeeQuery,BkPaginationQueryResult<EmployeeModel>>(newEmployeeQuery(filters),cancellationToken);

    }

    publicTask<BkPaginationQueryResult<EmployeeModel>>EmployeeQuery(int?pageSize=null,CancellationTokencancellationToken=default)

    {

        returndispatcher.Send<EmployeeQuery,BkPaginationQueryResult<EmployeeModel>>(newEmployeeQuery(pageSize),cancellationToken);

    }

    publicTask<BkPaginationQueryResult<BkAdminModel>>QueryApplicationUsers(BkFilterCollection<ApplicationUser>filters,CancellationTokencancellationToken=default)

    {

        returndispatcher.Send<QueryApplicationUsers,BkPaginationQueryResult<BkAdminModel>>(newQueryApplicationUsers(filters),cancellationToken);

    }

}

With this setup, you no longer need to remember or manually instantiate individual request types. Instead, you can rely on the injected interface to seamlessly interact with your domain logic in a clean and intuitive way.

Conclusion

The Domain Project is a cornerstone of the BlazorForKids framework. It’s where your entities come to life and where much of the framework’s automation and intelligence begins. From defining entities and models to generating complete sets of Mediator-style commands and queries, the Domain Project provides the foundation for all backend functionality in your application.

What we've shown so far only scratches the surface of what BlazorForKids is capable of. The real power lies in its ability to adapt and grow as you define properties, validation rules, or custom behaviors. To fully understand the depth and flexibility of the framework, we encourage you to explore the following pages:

  • Types of Properties Supported – Learn about all the property types you can define and how they influence generated code.
  • Validation Rules – Discover how the framework handles validation automatically and how you can extend it.
  • Custom Commands and Queries – Understand how to go beyond the generated commands with your own logic.
  • Working with BkFilterCollection – See how to use the powerful filtering engine to build rich, dynamic queries effortlessly.

BlazorForKids takes care of the boilerplate so you can focus on what matters—your application’s logic and experience.

For more details on how to configure entities please continue reading the sections below:

An unhandled error has occurred. Reload 🗙