Merge branch '2.0' into 2.0-docs
* 2.0: [Fixtures] Remove incorrect cap photo [Fixtures] Refresh user avatars [Fixtures] Remove old unused photos [UI] Reduce the margin on the product show page Add Sylius logo for e-mails [UI] Add missing asterix Fix mobile product attributes form Fix missing payment request breadcrumb Fix missing taxon header Fix mobile shipping methods form [Admin] Fix deleting customer account [UI] Add top margin to flash messages Fix mobile order header in admin panel Fix order payments and shipments tables in admin panel Remove obsolete catalog promotions validation adr Fix mobile admin navbar
|
|
@ -1,66 +0,0 @@
|
|||
# Catalog promotions validation
|
||||
|
||||
* Status: accepted
|
||||
* Date: 2022-02-09
|
||||
|
||||
## Context and Problem Statement
|
||||
|
||||
Catalog Promotions, as the new feature in Sylius 1.11, is developed in API-first manner, but of course provides
|
||||
UI functionality as well. The challenge, that we faced during the implementation was the validation, and either it should
|
||||
be unified for API and UI or separated.
|
||||
|
||||
## Decision Drivers
|
||||
|
||||
Catalog Promotions' validation should:
|
||||
* be easy to customize and extend
|
||||
* work on both API and UI
|
||||
* validate both business scenarios and syntactical correctness
|
||||
|
||||
## Considered Options
|
||||
|
||||
### Unified validation in custom validators
|
||||
|
||||
This is the first approach to validation we had. The concept was, to have
|
||||
[custom validator for each action and scope](https://github.com/Sylius/Sylius/blob/b5458fa31aadaf699e39b0cc106d5efd25144823/src/Sylius/Bundle/CoreBundle/Validator/CatalogPromotionAction/FixedDiscountActionValidator.php).
|
||||
The correct validation is chosen based on the key provided in `sylius.catalog_promotion.action_validator` tag. These custom
|
||||
validators should contain both business and syntactical validation (as the latter is not configured anywhere else).
|
||||
|
||||
* Good, because custom validators are unified for API and UI, which results in the lack of duplications
|
||||
* Bad, because business and syntactical validation is mixed, even though these are different type of validations
|
||||
* Bad, because it resulted in some problems in validation messages display (Catalog Promotions' forms are quite complicated)
|
||||
|
||||
### Unified business validation and separated syntactical validation
|
||||
|
||||
Variation of the previous option, which still uses custom validators but only for business (or functional) validation.
|
||||
Syntactical validation is delivered separately for UI and API.
|
||||
|
||||
* Good, because syntactical and business validation is separated, as it should be
|
||||
* Good, because UI validation on forms is less problematic still being extendable and customizable
|
||||
* Semi-bad, because it results in _apparent_ duplication of logic
|
||||
* Bad, because there is no easy way to syntactically validate requests in ApiPlatform, so it needs to be done in validators anyway
|
||||
|
||||
## Decision Outcome
|
||||
|
||||
Chosen option: **"Unified business validation and separated syntactical validation"**
|
||||
|
||||
Syntactical and functional validation are two different types of validation. They should be separated, especially as they
|
||||
can be approached differently (from the technical point of view) regarding the interface used (UI or API). Moreover, this
|
||||
separation gives us better control over how requests/forms are validated (e.g. some values passed in forms does not have
|
||||
to be validated in Sylius, as they're already protected in used Symfony types). Output of this decisions results in following
|
||||
structure of validators:
|
||||
|
||||
* `src/Sylius/Bundle/PromotionBundle/Validator/CatalogPromotionAction` - base business validation of catalog promotion actions
|
||||
not related to the concepts from other bundles (currently empty, only interface)
|
||||
* `src/Sylius/Bundle/CoreBundle/Validator/CatalogPromotionAction` - business validation of catalog promotion actions related
|
||||
to the concepts from other bundles (e.g. product variants)
|
||||
* `src/Sylius/Bundle/CoreBundle/Validator/CatalogPromotionScope` - business validation of catalog promotion scopes related
|
||||
to the concepts from other bundles (e.g. taxons)
|
||||
* `src/Sylius/Bundle/ApiBundle/Validator/CatalogPromotion` - syntactical validation of API requests; validators here usually
|
||||
decorate validators from the **CoreBundle**
|
||||
|
||||
Syntactical validation of forms data shall be done on each specific form (e.g. those from `src/Sylius/Bundle/CoreBundle/Form/Type/CatalogPromotionScope`).
|
||||
|
||||
## References
|
||||
|
||||
* [1st approach](https://github.com/Sylius/Sylius/pull/13174)
|
||||
* [Chosen approach](https://github.com/Sylius/Sylius/pull/13620)
|
||||
|
|
@ -9,12 +9,19 @@ Feature: Deleting the customer account
|
|||
And there is a user "theodore@example.com" identified by "pswd"
|
||||
And I am logged in as an administrator
|
||||
|
||||
@api @ui
|
||||
@api @no-ui
|
||||
Scenario: Deleting account should not delete customer details
|
||||
When I delete the account of "theodore@example.com" user
|
||||
Then the user account should be deleted
|
||||
But the customer with this email should still exist
|
||||
|
||||
@no-api @ui
|
||||
Scenario: Deleting account should not delete customer details
|
||||
When I delete the account of "theodore@example.com" user
|
||||
Then I should be notified that it has been successfully deleted
|
||||
And the customer should have no account
|
||||
But the customer with this email should still exist
|
||||
|
||||
@api @ui
|
||||
Scenario: A customer with no user cannot be deleted
|
||||
Given the account of "theodore@example.com" was deleted
|
||||
|
|
|
|||
|
|
@ -54,12 +54,11 @@ final class UserContext implements Context
|
|||
}
|
||||
|
||||
/**
|
||||
* @Then the user account should be deleted
|
||||
* @Then the customer should have no account
|
||||
*/
|
||||
public function accountShouldBeDeleted()
|
||||
public function theCustomerShouldHaveNoAccount(): void
|
||||
{
|
||||
$deletedUser = $this->sharedStorage->get('deleted_user');
|
||||
|
||||
$this->customerShowPage->open(['id' => $deletedUser->getCustomer()->getId()]);
|
||||
|
||||
Assert::false($this->customerShowPage->hasAccount());
|
||||
|
|
|
|||
|
|
@ -1,7 +1,8 @@
|
|||
{% set user = hookable_metadata.context.resource.user %}
|
||||
{% set customer = hookable_metadata.context.resource %}
|
||||
{% set user = customer.user %}
|
||||
|
||||
{% if user != null %}
|
||||
<form action="{{ path('sylius_admin_shop_user_delete', {'id': user.id}) }}" method="POST">
|
||||
<form action="{{ path('sylius_admin_shop_user_delete', {'id': user.id, 'customerId': customer.id}) }}" method="POST">
|
||||
<input type="hidden" name="_method" value="DELETE" />
|
||||
<input type="hidden" name="_csrf_token" value="{{ csrf_token(user.id) }}" />
|
||||
<button type="submit" class="dropdown-item" {{ sylius_test_html_attribute('customer-actions-delete') }}>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@
|
|||
{% set order = hookable_metadata.context.resource %}
|
||||
|
||||
<div class="col-12 col-md-6">
|
||||
<div class="d-md-flex gap-2 align-items-center">
|
||||
<div class="d-flex gap-2 align-items-center">
|
||||
<h1 class="page-title">
|
||||
{{ 'sylius.ui.order'|trans }}
|
||||
#{{ order.number }}
|
||||
|
|
|
|||
|
|
@ -2,15 +2,17 @@
|
|||
|
||||
<div class="py-3">
|
||||
{% if order.hasPayments %}
|
||||
<table class="table table-vcenter table-borderless table-sm card-table">
|
||||
<tbody>
|
||||
{% for payment in order.payments %}
|
||||
<tr {{ sylius_test_html_attribute('payment') }}>
|
||||
{% hook 'item' with { resource: order, payment } %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-vcenter table-borderless table-sm card-table">
|
||||
<tbody>
|
||||
{% for payment in order.payments %}
|
||||
<tr {{ sylius_test_html_attribute('payment') }}>
|
||||
{% hook 'item' with { resource: order, payment } %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<span class="px-3 text-secondary italic" {{ sylius_test_html_attribute('no-payments') }}>{{ 'sylius.ui.no_payments'|trans }}</span>
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<td class="w-1">
|
||||
<td class="w-1 px-2">
|
||||
<div class="d-flex justify-content-end">
|
||||
{% hook 'actions' %}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{% set shipment = hookable_metadata.context.shipment %}
|
||||
|
||||
<td class="px-3 py-2">
|
||||
<td class="px-3 py-2 text-nowrap">
|
||||
<div class="py-2">
|
||||
<strong {{ sylius_test_html_attribute('shipment-method') }}>{{ shipment.method.name }}</strong>
|
||||
<div class="text-secondary">{{ ux_icon('tabler:world') }} {{ shipment.method.zone }}</div>
|
||||
|
|
|
|||
|
|
@ -2,15 +2,17 @@
|
|||
|
||||
<div class="py-3">
|
||||
{% if order.hasShipments %}
|
||||
<table class="table table-vcenter table-borderless table-sm card-table">
|
||||
<tbody>
|
||||
{% for shipment in order.shipments %}
|
||||
<tr {{ sylius_test_html_attribute('shipment') }}>
|
||||
{% hook 'item' with { shipment } %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-vcenter table-borderless table-sm card-table">
|
||||
<tbody>
|
||||
{% for shipment in order.shipments %}
|
||||
<tr {{ sylius_test_html_attribute('shipment') }}>
|
||||
{% hook 'item' with { shipment } %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<span class="px-3 text-secondary italic">{{ 'sylius.ui.there_are_no_shipments_to_display'|trans }}</span>
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -5,6 +5,6 @@
|
|||
{{ breadcrumbs([
|
||||
{ name: 'sylius.ui.dashboard', url: path('sylius_admin_dashboard'), active: false },
|
||||
{ name: 'sylius.ui.payments'|trans, url: path('sylius_admin_payment_index'), active: false },
|
||||
{ name: configuration.request.get('id'), active: false },
|
||||
{ name: configuration.request.get('paymentId'), active: true },
|
||||
{ name: 'sylius.ui.payment_requests'|trans, active: true },
|
||||
]) }}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
<div class="col-6">
|
||||
<div class="col-12 col-md-6">
|
||||
{{ form_row(hookable_metadata.context.form.code, sylius_test_form_attribute('code')) }}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
{% if hookable_metadata.context.form.position is defined %}
|
||||
<div class="col-6">
|
||||
<div class="col-12 col-md-6">
|
||||
{{ form_row(hookable_metadata.context.form.position, sylius_test_form_attribute('position')) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
<div class="col-6">
|
||||
<div class="col-12 col-md-6">
|
||||
{{ form_row(hookable_metadata.context.form.translatable, sylius_test_form_attribute('translatable')) }}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
<div class="col-6">
|
||||
<div class="col-12 col-md-6">
|
||||
{{ form_row(hookable_metadata.context.form.type, sylius_test_form_attribute('type')) }}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
<div class="row mt-3">
|
||||
<div class="row mt-3 row-gap-2">
|
||||
{% hook 'title_block' %}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
<header class="navbar d-print-none">
|
||||
<div class="container-xl d-flex">
|
||||
<div class="container-xl d-flex gap-2 flex-nowrap">
|
||||
{% hook 'sylius_admin.common.component.navbar' with { _prefixes: [] } %}
|
||||
</div>
|
||||
</header>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
<div class="nav align-items-center gap-3">
|
||||
<div class="nav align-items-center gap-3 flex-nowrap flex-shrink-0">
|
||||
{% hook 'items' %}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
<div class="d-flex flex-wrap sm-align-items-center gap-3" id="navbar-menu">
|
||||
<div class="d-flex flex-wrap gap-3 flex-shrink-1" id="navbar-menu">
|
||||
{% hook 'menu' %}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -14,10 +14,10 @@
|
|||
{% hook 'form' with { form } %}
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-6">
|
||||
<div class="col-12 col-md-6">
|
||||
{% hook 'form#left' with { form } %}
|
||||
</div>
|
||||
<div class="col-6">
|
||||
<div class="col-12 col-md-6">
|
||||
{% hook 'form#right' with { form } %}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -270,7 +270,6 @@ sylius_fixtures:
|
|||
images:
|
||||
- { path: '@SyliusCoreBundle/Resources/fixtures/caps/cap_13_1.jpeg', type: 'main' }
|
||||
- { path: '@SyliusCoreBundle/Resources/fixtures/caps/cap_13_2.jpeg' }
|
||||
- { path: '@SyliusCoreBundle/Resources/fixtures/caps/cap_13_3.jpeg' }
|
||||
|
||||
- name: 'Adventurous Aurora Cap'
|
||||
tax_category: 'other'
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 100 KiB After Width: | Height: | Size: 161 KiB |
|
Before Width: | Height: | Size: 214 KiB After Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 263 KiB |
|
Before Width: | Height: | Size: 245 KiB |
|
Before Width: | Height: | Size: 382 KiB |
|
Before Width: | Height: | Size: 101 KiB |
BIN
src/Sylius/Bundle/ShopBundle/Resources/assets/images/logo.png
Normal file
|
After Width: | Height: | Size: 7.2 KiB |
|
|
@ -1,4 +1,5 @@
|
|||
.form-label.required::after {
|
||||
.form-label.required::after,
|
||||
.col-form-label.required::after {
|
||||
content: "*";
|
||||
padding-left: 3px;
|
||||
color: $danger;
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@
|
|||
|
||||
.sylius-messages:has(*) {
|
||||
margin-top: 1.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.alert {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,3 @@
|
|||
<div class="mb-4">
|
||||
{% hook 'sylius_shop.product.index.page.body.main.header' with { taxon } %}
|
||||
{% hook 'sylius_shop.product.index.content.body.main.header' with { taxon } %}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -3,7 +3,5 @@
|
|||
{% block title %}{{ product.name }} | {{ parent() }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container mt-4 mb-5">
|
||||
{% hook 'sylius_shop.product.show' with { product, configuration } %}
|
||||
</div>
|
||||
{% hook 'sylius_shop.product.show' with { product, configuration } %}
|
||||
{% endblock %}
|
||||
|
|
|
|||