Feat/optimisation user account (#773)
* removed place registration on front page * phone field is mandatory * added address verification on a product demand * fix test
This commit is contained in:
parent
69014eb69e
commit
e6ace1d40f
18 changed files with 87 additions and 112 deletions
|
|
@ -88,7 +88,6 @@ App\Entity\User:
|
|||
name: 'APES compte lieu'
|
||||
address: '@address_region_hauts_de_france'
|
||||
schedule: '9h30 - 17h30'
|
||||
phoneNumber: null
|
||||
createdAt: <date_create_immutable('+1 month')>
|
||||
|
||||
# —— Users —————————————————————————————————————————————————————————————————
|
||||
|
|
@ -170,6 +169,7 @@ App\Entity\User:
|
|||
firstname: <firstname()>
|
||||
lastname: <lastname()>
|
||||
address: null
|
||||
phoneNumber: '+33600000000'
|
||||
avatar: 'a9a9bf49-24e4-4b3e-bdbd-86808c32939e.jpg'
|
||||
|
||||
# user with an address and a preferred category set
|
||||
|
|
|
|||
|
|
@ -342,7 +342,7 @@ abstract class AbstractUserCrudController extends AbstractCrudController impleme
|
|||
->setFormType(PhoneNumberType::class)
|
||||
->setFormTypeOptions([
|
||||
'format' => PhoneNumberFormat::INTERNATIONAL,
|
||||
'required' => false,
|
||||
'required' => true,
|
||||
])
|
||||
->setHelp($i18prefix.'.field.phone.help')
|
||||
;
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@ use App\Controller\FlashTrait;
|
|||
use App\Controller\i18nTrait;
|
||||
use App\Controller\User\MyAccountAction;
|
||||
use App\Entity\User;
|
||||
use App\Enum\User\UserType;
|
||||
use App\Exception\UserConfirmationTokenExpiredException;
|
||||
use App\Exception\UserNotFoundException;
|
||||
use App\Form\Type\Security\AccountCreateStep1FormType;
|
||||
|
|
@ -105,6 +106,7 @@ final class AccountCreateController extends AbstractController
|
|||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
/** @var User $user */
|
||||
$user = $form->getData();
|
||||
$user->setType(UserType::USER);
|
||||
$this->commandBus->dispatch(new AccountCreateStep2Command($user));
|
||||
$this->security->login($user); // auto-log the user
|
||||
|
||||
|
|
|
|||
|
|
@ -5,11 +5,10 @@ declare(strict_types=1);
|
|||
namespace App\Form\Type\Security;
|
||||
|
||||
use App\Entity\User;
|
||||
use App\Enum\User\UserType;
|
||||
use libphonenumber\PhoneNumberFormat;
|
||||
use Misd\PhoneNumberBundle\Form\Type\PhoneNumberType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\CallbackTransformer;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\ChoiceType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\PasswordType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\RepeatedType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\SubmitType;
|
||||
|
|
@ -27,19 +26,6 @@ final class AccountCreateStep2FormType extends AbstractType
|
|||
public function buildForm(FormBuilderInterface $builder, array $options): void
|
||||
{
|
||||
$builder
|
||||
->add('type', ChoiceType::class, [
|
||||
'label' => 'account_create_action.account_type',
|
||||
'label_attr' => ['class' => 'text-black fw-light'],
|
||||
'choices' => UserType::getForFront(),
|
||||
'choice_attr' => function () {
|
||||
return [
|
||||
'data-controller' => 'account',
|
||||
'data-action' => 'click->account#choosenType',
|
||||
];
|
||||
},
|
||||
'expanded' => true,
|
||||
])
|
||||
|
||||
->add('firstname', TextType::class, [
|
||||
'label' => 'account_create_action.firsname',
|
||||
'label_attr' => ['class' => 'text-black fw-light required'],
|
||||
|
|
@ -60,14 +46,14 @@ final class AccountCreateStep2FormType extends AbstractType
|
|||
'required' => false,
|
||||
])
|
||||
|
||||
->add('name', TextType::class, [
|
||||
'label' => 'account_create_action.name',
|
||||
'label_attr' => ['class' => 'text-black fw-light required'],
|
||||
'attr' => [
|
||||
'class' => 'form-control-sm input-name',
|
||||
'placeholder' => 'account_create_action.name.placeholder',
|
||||
],
|
||||
'required' => false,
|
||||
->add('phone', PhoneNumberType::class, [
|
||||
'label' => 'account_create_action.phone',
|
||||
'label_attr' => ['class' => 'text-black fs-6 fw-normal'],
|
||||
'widget' => PhoneNumberType::WIDGET_COUNTRY_CHOICE,
|
||||
'format' => PhoneNumberFormat::INTERNATIONAL,
|
||||
'country_display_emoji_flag' => true,
|
||||
'preferred_country_choices' => ['FR'],
|
||||
'required' => true,
|
||||
])
|
||||
|
||||
->add('plainPassword', RepeatedType::class, [
|
||||
|
|
@ -118,15 +104,6 @@ final class AccountCreateStep2FormType extends AbstractType
|
|||
'attr' => ['class' => 'btn btn-primary btn-sm'],
|
||||
])
|
||||
;
|
||||
|
||||
$builder->get('type')->addModelTransformer(new CallbackTransformer(
|
||||
function (?UserType $enumToString) {
|
||||
return $enumToString === null ? '' : $enumToString->value;
|
||||
},
|
||||
function (string $stringToEnum) {
|
||||
return UserType::from($stringToEnum);
|
||||
}
|
||||
));
|
||||
}
|
||||
|
||||
public function configureOptions(OptionsResolver $resolver): void
|
||||
|
|
|
|||
|
|
@ -50,8 +50,9 @@ final class EditProfileFormType extends AbstractType
|
|||
'label_attr' => ['class' => 'text-black fs-6 fw-normal'],
|
||||
'format' => PhoneNumberFormat::INTERNATIONAL,
|
||||
'widget' => PhoneNumberType::WIDGET_COUNTRY_CHOICE,
|
||||
'country_display_emoji_flag' => true,
|
||||
'preferred_country_choices' => ['FR'],
|
||||
'required' => false,
|
||||
'required' => true,
|
||||
])
|
||||
|
||||
->add('smsNotifications', CheckboxType::class, [
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ namespace App\Message\Command\Security;
|
|||
use App\Entity\User;
|
||||
use App\Enum\User\UserType;
|
||||
use App\MessageHandler\Command\Security\AccountCreateStep2CommandHandler;
|
||||
use libphonenumber\PhoneNumber;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
|
|
@ -22,6 +23,7 @@ final class AccountCreateStep2Command
|
|||
public ?string $firstname = null;
|
||||
public ?string $name = null;
|
||||
public string $plainPassword;
|
||||
public PhoneNumber $phone;
|
||||
|
||||
public function __construct(User $user)
|
||||
{
|
||||
|
|
@ -33,5 +35,7 @@ final class AccountCreateStep2Command
|
|||
$this->name = $user->getName();
|
||||
Assert::stringNotEmpty($user->getPlainPassword());
|
||||
$this->plainPassword = $user->getPlainPassword();
|
||||
Assert::notNull($user->phone);
|
||||
$this->phone = $user->phone;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,6 +9,8 @@ use App\Entity\User;
|
|||
use App\Enum\User\UserType;
|
||||
use App\Message\Command\Security\AccountCreateStep2Command;
|
||||
use App\Repository\UserRepository;
|
||||
use libphonenumber\PhoneNumberFormat;
|
||||
use libphonenumber\PhoneNumberUtil;
|
||||
use Symfony\Component\Messenger\Attribute\AsMessageHandler;
|
||||
use Webmozart\Assert\Assert;
|
||||
|
||||
|
|
@ -47,6 +49,11 @@ final class AccountCreateStep2CommandHandler
|
|||
throw new \UnexpectedValueException('This hanlder can only create users or places.');
|
||||
}
|
||||
|
||||
$phoneObjectToString = PhoneNumberUtil::getInstance()->format(
|
||||
$message->phone,
|
||||
PhoneNumberFormat::E164
|
||||
);
|
||||
$user->setPhoneNumber($phoneObjectToString);
|
||||
$this->userManager->updatePassword($user->setPlainPassword($message->plainPassword));
|
||||
$this->userManager->finalizeAccountCreateStep2($user);
|
||||
$this->userManager->save($user, true);
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
{% set is_product_owner = product.owner == app.user %}
|
||||
|
||||
<div class="bg-light rounded-2 p-3" data-controller="calendar" data-calendar-unavailabilities-value="{{ product.getUnavailabilities()|join(',') }}">
|
||||
<div class="bg-light rounded-2 p-3" data-controller="calendar"
|
||||
data-calendar-unavailabilities-value="{{ product.getUnavailabilities()|join(',') }}">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<h5>{{ title }}</h5>
|
||||
|
|
@ -71,22 +72,36 @@
|
|||
</div>
|
||||
{% endif %}
|
||||
<div class="d-grid col mt-3">
|
||||
<button class="btn border border-0 text-primary text-decoration-underline" data-action="click->calendar#resetDates" type="button">
|
||||
<button class="btn border border-0 text-primary text-decoration-underline"
|
||||
data-action="click->calendar#resetDates" type="button">
|
||||
{{ 'templates.components.product.calendar.reset'|trans }}
|
||||
</button>
|
||||
</div>
|
||||
{% if actionNeeded %}
|
||||
{% if actionNeeded %}
|
||||
<div class="d-grid col-12 mt-3">
|
||||
{% if app.user is null or is_granted('borrow', product) %}
|
||||
<button
|
||||
id="service-request"
|
||||
class="btn btn-sm btn-primary"
|
||||
data-path="{{ path('app_user_service_request_new', {id: product.id}) }}"
|
||||
data-action="click->calendar#serviceRequest"
|
||||
disabled
|
||||
>
|
||||
{{ 'templates.components.product.calendar.service_request'|trans }}
|
||||
</button>
|
||||
{% if app.user and app.user.address is null %}
|
||||
{% include 'components/product/_modal.html.twig' with {
|
||||
menu_action: false,
|
||||
page_type: 'article',
|
||||
button: 'templates.components.product.calendar.service_request'|trans,
|
||||
title: 'templates.pages.account.index.no-address-title'|trans,
|
||||
message: 'templates.pages.account.index.no-address-message'|trans({
|
||||
'%product%': product.type.isObject ? 'objet' : 'service'
|
||||
}),
|
||||
action: 'templates.pages.account.product.list.no-address-add'|trans
|
||||
} %}
|
||||
{% else %}
|
||||
<button
|
||||
id="service-request"
|
||||
class="btn btn-sm btn-primary"
|
||||
data-path="{{ path('app_user_service_request_new', {id: product.id}) }}"
|
||||
data-action="click->calendar#serviceRequest"
|
||||
disabled
|
||||
>
|
||||
{{ 'templates.components.product.calendar.service_request'|trans }}
|
||||
</button>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{{ form_widget(form.submit) }}
|
||||
{% endif %}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,14 @@
|
|||
{% if menu_action is defined and menu_action == true %}
|
||||
<span class="text-decoration-none text-primary ms-2 position-relative pe-1 cursor-pointer"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#modalAddAddress">{{ button }}</span>
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#modalAddAddress">{{ button }}</span>
|
||||
{% elseif menu_action is defined and menu_action == false and page_type == 'article' %}
|
||||
<button type="button"
|
||||
class="btn btn-primary"
|
||||
data-bs-toggle="modal"
|
||||
data-bs-target="#modalAddAddress">
|
||||
{{ button }}
|
||||
</button>
|
||||
{% else %}
|
||||
<div class="d-grid col-12 col-lg-4 mx-auto my-3 order-last">
|
||||
<button type="button"
|
||||
|
|
@ -32,7 +39,7 @@
|
|||
<button type="button"
|
||||
class="btn btn-outline-secondary"
|
||||
data-bs-dismiss="modal">
|
||||
{{ (i18n_prefix ~ '.no-address-cancel')|trans }}
|
||||
{{ 'templates.pages.account.product.list.no-address-cancel'|trans }}
|
||||
</button>
|
||||
<a href="{{ path('user_address_step1') }}"
|
||||
class="btn btn-secondary">
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@
|
|||
|
||||
{{ form_start(form, {attr: {novalidate: true}}) }}
|
||||
|
||||
{{ form_row(form.type) }}
|
||||
<div class="user-input mt-2">
|
||||
{{ form_label(form.firstname) }}
|
||||
{{ form_widget(form.firstname) }}
|
||||
|
|
@ -40,10 +39,10 @@
|
|||
{{ form_widget(form.lastname) }}
|
||||
{{ form_errors(form.lastname) }}
|
||||
</div>
|
||||
<div class="place-input mt-2">
|
||||
{{ form_label(form.name) }}
|
||||
{{ form_widget(form.name) }}
|
||||
{{ form_errors(form.name) }}
|
||||
<div class="phone-input mt-2">
|
||||
{{ form_label(form.phone) }}
|
||||
{{ form_widget(form.phone) }}
|
||||
{{ form_errors(form.phone) }}
|
||||
</div>
|
||||
|
||||
{% include 'components/form/_password_visibility.html.twig' with {
|
||||
|
|
|
|||
|
|
@ -1,53 +0,0 @@
|
|||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace App\Tests\Functional\Controller\Security;
|
||||
|
||||
use App\Test\ContainerRepositoryTrait;
|
||||
use App\Test\KernelTrait;
|
||||
use App\Tests\TestReference;
|
||||
use Hautelook\AliceBundle\PhpUnit\RefreshDatabaseTrait;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
||||
use Symfony\Component\String\ByteString;
|
||||
|
||||
/**
|
||||
* @see AccountCreateController
|
||||
*/
|
||||
final class AccountCreateActionStep2PlaceTest extends WebTestCase
|
||||
{
|
||||
use ContainerRepositoryTrait;
|
||||
use RefreshDatabaseTrait;
|
||||
use KernelTrait;
|
||||
|
||||
private const ROUTE = '/fr/compte/creer-mon-compte-etape-2/';
|
||||
|
||||
public function testUserConfirmationTokenExpiredException(): void
|
||||
{
|
||||
$client = self::createClient();
|
||||
$client->request('GET', self::ROUTE.TestReference::USER_13_CONFIRMATION_TOKEN);
|
||||
self::assertResponseRedirects();
|
||||
$client->followRedirect();
|
||||
self::assertResponseIsSuccessful();
|
||||
self::assertSelectorTextContains('body', 'app.controller.security.account_create_controller.step2.user_confirmation_token_expired.warning');
|
||||
}
|
||||
|
||||
public function testFormSubmitPlaceSuccess(): void
|
||||
{
|
||||
$client = self::createClient();
|
||||
$crawler = $client->request('GET', self::ROUTE.TestReference::USER_12_CONFIRMATION_TOKEN);
|
||||
$form = $crawler->selectButton('account_create_step2_form_submit')->form();
|
||||
|
||||
$password = ByteString::fromRandom(13);
|
||||
$client->submit($form, [
|
||||
$form->getName().'[type]' => 'place',
|
||||
$form->getName().'[name]' => 'My Association',
|
||||
$form->getName().'[plainPassword][first]' => $password,
|
||||
$form->getName().'[plainPassword][second]' => $password,
|
||||
$form->getName().'[gdpr]' => 1,
|
||||
]);
|
||||
self::assertResponseRedirects();
|
||||
$client->followRedirect();
|
||||
self::assertResponseIsSuccessful();
|
||||
}
|
||||
}
|
||||
|
|
@ -33,11 +33,12 @@ final class AccountCreateActionStep2UserInvitationTest extends WebTestCase
|
|||
|
||||
$password = ByteString::fromRandom(13);
|
||||
$client->submit($form, [
|
||||
$form->getName().'[type]' => 'user',
|
||||
$form->getName().'[firstname]' => 'Foo',
|
||||
$form->getName().'[lastname]' => 'Bar',
|
||||
$form->getName().'[plainPassword][first]' => $password,
|
||||
$form->getName().'[plainPassword][second]' => $password,
|
||||
$form->getName().'[phone][country]' => 'FR',
|
||||
$form->getName().'[phone][number]' => '602030405',
|
||||
$form->getName().'[gdpr]' => 1,
|
||||
]);
|
||||
self::assertResponseRedirects();
|
||||
|
|
|
|||
|
|
@ -40,11 +40,12 @@ final class AccountCreateActionStep2UserTest extends WebTestCase
|
|||
|
||||
$password = ByteString::fromRandom(13);
|
||||
$client->submit($form, [
|
||||
$form->getName().'[type]' => 'user',
|
||||
$form->getName().'[firstname]' => 'Foo',
|
||||
$form->getName().'[lastname]' => 'Bar',
|
||||
$form->getName().'[plainPassword][first]' => $password,
|
||||
$form->getName().'[plainPassword][second]' => $password,
|
||||
$form->getName().'[phone][country]' => 'FR',
|
||||
$form->getName().'[phone][number]' => '602030405',
|
||||
$form->getName().'[gdpr]' => 1,
|
||||
]);
|
||||
self::assertResponseRedirects();
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ final class UserManagerTest extends KernelTestCase
|
|||
|
||||
$user = new User();
|
||||
$user->setEmail(ByteString::fromRandom(6)->toString().'@example.com');
|
||||
$user->setPhoneNumber('+33600000000');
|
||||
$user->setPassword('foo');
|
||||
|
||||
$userManager->save($user, true);
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ use App\Message\Command\Security\AccountCreateStep2Command;
|
|||
use App\MessageHandler\Command\Security\AccountCreateStep2CommandHandler;
|
||||
use App\Tests\TestReference;
|
||||
use Hautelook\AliceBundle\PhpUnit\RefreshDatabaseTrait;
|
||||
use libphonenumber\PhoneNumberUtil;
|
||||
use Symfony\Bundle\FrameworkBundle\Test\KernelTestCase;
|
||||
use Symfony\Component\Uid\Uuid;
|
||||
|
||||
|
|
@ -25,11 +26,17 @@ final class AccountCreateStep2CommandHandlerTest extends KernelTestCase
|
|||
|
||||
$this->expectException(\UnexpectedValueException::class);
|
||||
$this->expectExceptionMessage('This hanlder can only create users or places');
|
||||
|
||||
$user = (new User())
|
||||
->setId(Uuid::fromString(TestReference::USER_17))
|
||||
->setType(UserType::ADMIN)
|
||||
->setPlainPassword('foo')
|
||||
;
|
||||
|
||||
$phoneUtil = PhoneNumberUtil::getInstance();
|
||||
$phone = $phoneUtil->parse('+33602030405', 'FR');
|
||||
$user->phone = $phone;
|
||||
|
||||
$message = new AccountCreateStep2Command($user);
|
||||
$handler($message);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ final class UserRepositoryTest extends KernelTestCase
|
|||
|
||||
$user = new User();
|
||||
$user->setEmail(ByteString::fromRandom(6)->toString().'@example.com');
|
||||
$user->setPhoneNumber('+33600000000');
|
||||
$user->setPassword('foo');
|
||||
$repo->save($user, true);
|
||||
$count = $repo->count([]);
|
||||
|
|
|
|||
|
|
@ -75,6 +75,11 @@
|
|||
<target>8 caractères minimun</target>
|
||||
</trans-unit>
|
||||
|
||||
<trans-unit id="3Wd6Kp0" resname="account_create_action.phone">
|
||||
<source>account_create_action.phone</source>
|
||||
<target>Numéro de téléphone</target>
|
||||
</trans-unit>
|
||||
|
||||
<trans-unit id="m3UMrJf" resname="account_create_action.gdpr">
|
||||
<source>account_create_action.gdpr</source>
|
||||
<target>J’ai lu et j’accepte les <![CDATA[<a href="%link%" class="text-primary">Conditions Générales d'Utilisation</a>]]></target>
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@
|
|||
|
||||
<trans-unit id="ftBMsiP" resname="templates.pages.account.index.no-address-message">
|
||||
<source>templates.pages.account.index.no-address-message</source>
|
||||
<target>Pour pouvoir créer un %product%, commencez par remplir votre adresse.</target>
|
||||
<target>Pour pouvoir créer ou emprunter un %product%, commencez par remplir votre adresse.</target>
|
||||
</trans-unit>
|
||||
|
||||
<trans-unit id="e8xvcp6" resname="templates.pages.account.index.no-address-cancel">
|
||||
|
|
|
|||
Loading…
Reference in a new issue