feat/login #10
17
.env
17
.env
@@ -1,10 +1,11 @@
|
||||
APP_ENV=dev
|
||||
APP_SECRET=je_te_remplis_parce_que_tu_me_mets_des_messages_d_erreur
|
||||
APP_SHARE_DIR=var/share
|
||||
APP_VERSION=0.0.1
|
||||
DATABASE_URL="postgresql://app:!ChangeMe!@127.0.0.1:5432/app?serverVersion=16&charset=utf8"
|
||||
MESSENGER_TRANSPORT_DSN="doctrine://default"
|
||||
MAILER_DSN="smtp://localhost:1025"
|
||||
DEFAULT_URI="http://localhost:8000"
|
||||
APP_ENV=
|
||||
APP_SECRET=
|
||||
APP_SHARE_DIR=
|
||||
APP_VERSION=
|
||||
DATABASE_URL=
|
||||
MESSENGER_TRANSPORT_DSN=
|
||||
MAILER_DSN=
|
||||
DEFAULT_URI=
|
||||
KAZ_API_BASE_URL=
|
||||
KAZ_API_USER=
|
||||
KAZ_API_PASSWORD=
|
||||
|
||||
@@ -30,6 +30,6 @@
|
||||
--color-gris-fonce: #4B5563;
|
||||
|
||||
/* Polices */
|
||||
--font-sora: "Sora", system-ui, sans-serif;
|
||||
--font-sora: "Sora", sans-serif;
|
||||
--font-caveat: "Caveat", cursive;
|
||||
}
|
||||
|
||||
12
composer.lock
generated
12
composer.lock
generated
@@ -3686,16 +3686,16 @@
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-client",
|
||||
"version": "v8.0.5",
|
||||
"version": "v8.0.8",
|
||||
"source": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/symfony/http-client.git",
|
||||
"reference": "f9fdd372473e66469c6d32a4ed12efcffdea38c4"
|
||||
"reference": "356e43d6994ae9d7761fd404d40f78691deabe0e"
|
||||
},
|
||||
"dist": {
|
||||
"type": "zip",
|
||||
"url": "https://api.github.com/repos/symfony/http-client/zipball/f9fdd372473e66469c6d32a4ed12efcffdea38c4",
|
||||
"reference": "f9fdd372473e66469c6d32a4ed12efcffdea38c4",
|
||||
"url": "https://api.github.com/repos/symfony/http-client/zipball/356e43d6994ae9d7761fd404d40f78691deabe0e",
|
||||
"reference": "356e43d6994ae9d7761fd404d40f78691deabe0e",
|
||||
"shasum": ""
|
||||
},
|
||||
"require": {
|
||||
@@ -3758,7 +3758,7 @@
|
||||
"http"
|
||||
],
|
||||
"support": {
|
||||
"source": "https://github.com/symfony/http-client/tree/v8.0.5"
|
||||
"source": "https://github.com/symfony/http-client/tree/v8.0.8"
|
||||
},
|
||||
"funding": [
|
||||
{
|
||||
@@ -3778,7 +3778,7 @@
|
||||
"type": "tidelift"
|
||||
}
|
||||
],
|
||||
"time": "2026-01-27T16:18:07+00:00"
|
||||
"time": "2026-03-30T15:14:47+00:00"
|
||||
},
|
||||
{
|
||||
"name": "symfony/http-client-contracts",
|
||||
|
||||
@@ -16,7 +16,7 @@ services:
|
||||
$apiUser: '%env(KAZ_API_USER)%'
|
||||
$apiPassword: '%env(KAZ_API_PASSWORD)%'
|
||||
|
||||
# Gestion de l'enregistrement de la photo de profil
|
||||
# Gestion de l'enregistrement de l'image de profil
|
||||
App\Service\FileUploader:
|
||||
arguments:
|
||||
$targetDirectory: '%images_directory%'
|
||||
|
||||
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20260313151403 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE TABLE "user" (id UUID NOT NULL, email VARCHAR(180) NOT NULL, roles JSON NOT NULL, password VARCHAR(255) NOT NULL, email_quota VARCHAR(255) NOT NULL, alternate_email VARCHAR(255) NOT NULL, identifiant_kaz VARCHAR(255) NOT NULL, quota VARCHAR(255) NOT NULL, has_nextcloud_access BOOLEAN NOT NULL, nextcloud_quota VARCHAR(255) NOT NULL, has_mobilizon BOOLEAN NOT NULL, has_agora_access BOOLEAN NOT NULL, lastname VARCHAR(255) NOT NULL, firstname VARCHAR(255) NOT NULL, PRIMARY KEY (id))');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL ON "user" (email)');
|
||||
$this->addSql('CREATE TABLE messenger_messages (id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, body TEXT NOT NULL, headers TEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, available_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, delivered_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY (id))');
|
||||
$this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0E3BD61CE16BA31DBBF396750 ON messenger_messages (queue_name, available_at, delivered_at, id)');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('DROP TABLE "user"');
|
||||
$this->addSql('DROP TABLE messenger_messages');
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20260316103235 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE "user" ALTER alternate_email DROP NOT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE "user" ALTER alternate_email SET NOT NULL');
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20260316104254 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE "user" ALTER identifiant_kaz DROP NOT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE "user" ALTER identifiant_kaz SET NOT NULL');
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20260316104335 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE "user" ALTER quota DROP NOT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE "user" ALTER quota SET NOT NULL');
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20260316104505 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE "user" ALTER has_agora_access DROP NOT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE "user" ALTER has_agora_access SET NOT NULL');
|
||||
}
|
||||
}
|
||||
@@ -1,33 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20260316104557 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE "user" ALTER has_nextcloud_access DROP NOT NULL');
|
||||
$this->addSql('ALTER TABLE "user" ALTER has_mobilizon DROP NOT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE "user" ALTER has_nextcloud_access SET NOT NULL');
|
||||
$this->addSql('ALTER TABLE "user" ALTER has_mobilizon SET NOT NULL');
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20260316114715 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE "user" ADD last_name VARCHAR(255) NOT NULL');
|
||||
$this->addSql('ALTER TABLE "user" ADD first_name VARCHAR(255) NOT NULL');
|
||||
$this->addSql('ALTER TABLE "user" DROP lastname');
|
||||
$this->addSql('ALTER TABLE "user" DROP firstname');
|
||||
$this->addSql('ALTER TABLE "user" ALTER alternate_email SET NOT NULL');
|
||||
$this->addSql('ALTER TABLE "user" ALTER identifiant_kaz SET NOT NULL');
|
||||
$this->addSql('ALTER TABLE "user" ALTER quota SET NOT NULL');
|
||||
$this->addSql('ALTER TABLE "user" ALTER has_nextcloud_access SET NOT NULL');
|
||||
$this->addSql('ALTER TABLE "user" ALTER has_mobilizon SET NOT NULL');
|
||||
$this->addSql('ALTER TABLE "user" ALTER has_agora_access SET NOT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE "user" ADD lastname VARCHAR(255) NOT NULL');
|
||||
$this->addSql('ALTER TABLE "user" ADD firstname VARCHAR(255) NOT NULL');
|
||||
$this->addSql('ALTER TABLE "user" DROP last_name');
|
||||
$this->addSql('ALTER TABLE "user" DROP first_name');
|
||||
$this->addSql('ALTER TABLE "user" ALTER alternate_email DROP NOT NULL');
|
||||
$this->addSql('ALTER TABLE "user" ALTER identifiant_kaz DROP NOT NULL');
|
||||
$this->addSql('ALTER TABLE "user" ALTER quota DROP NOT NULL');
|
||||
$this->addSql('ALTER TABLE "user" ALTER has_nextcloud_access DROP NOT NULL');
|
||||
$this->addSql('ALTER TABLE "user" ALTER has_mobilizon DROP NOT NULL');
|
||||
$this->addSql('ALTER TABLE "user" ALTER has_agora_access DROP NOT NULL');
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20260326214353 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE "user" ADD photo VARCHAR(255) DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE "user" DROP photo');
|
||||
}
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20260326231417 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE "user" ADD telephone VARCHAR(20) DEFAULT NULL');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('ALTER TABLE "user" DROP telephone');
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20260328101039 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE TABLE "user" (id UUID NOT NULL, email VARCHAR(180) NOT NULL, roles JSON NOT NULL, password VARCHAR(255) NOT NULL, email_quota VARCHAR(255) NOT NULL, alternate_email VARCHAR(255) NOT NULL, identifiant_kaz VARCHAR(255) NOT NULL, quota VARCHAR(255) NOT NULL, has_nextcloud_access BOOLEAN NOT NULL, nextcloud_quota VARCHAR(255) NOT NULL, has_mobilizon BOOLEAN NOT NULL, has_agora_access BOOLEAN NOT NULL, last_name VARCHAR(255) NOT NULL, first_name VARCHAR(255) NOT NULL, photo VARCHAR(255) DEFAULT NULL, telephone VARCHAR(20) DEFAULT NULL, PRIMARY KEY (id))');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL ON "user" (email)');
|
||||
$this->addSql('CREATE TABLE messenger_messages (id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, body TEXT NOT NULL, headers TEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, available_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, delivered_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY (id))');
|
||||
$this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0E3BD61CE16BA31DBBF396750 ON messenger_messages (queue_name, available_at, delivered_at, id)');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('DROP TABLE "user"');
|
||||
$this->addSql('DROP TABLE messenger_messages');
|
||||
}
|
||||
}
|
||||
@@ -1,35 +0,0 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace DoctrineMigrations;
|
||||
|
||||
use Doctrine\DBAL\Schema\Schema;
|
||||
use Doctrine\Migrations\AbstractMigration;
|
||||
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20260328101220 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
return '';
|
||||
}
|
||||
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE TABLE "user" (id UUID NOT NULL, email VARCHAR(180) NOT NULL, roles JSON NOT NULL, password VARCHAR(255) NOT NULL, email_quota VARCHAR(255) NOT NULL, alternate_email VARCHAR(255) NOT NULL, identifiant_kaz VARCHAR(255) NOT NULL, quota VARCHAR(255) NOT NULL, has_nextcloud_access BOOLEAN NOT NULL, nextcloud_quota VARCHAR(255) NOT NULL, has_mobilizon BOOLEAN NOT NULL, has_agora_access BOOLEAN NOT NULL, last_name VARCHAR(255) NOT NULL, first_name VARCHAR(255) NOT NULL, photo VARCHAR(255) DEFAULT NULL, telephone VARCHAR(20) DEFAULT NULL, PRIMARY KEY (id))');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL ON "user" (email)');
|
||||
$this->addSql('CREATE TABLE messenger_messages (id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, body TEXT NOT NULL, headers TEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, available_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, delivered_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY (id))');
|
||||
$this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0E3BD61CE16BA31DBBF396750 ON messenger_messages (queue_name, available_at, delivered_at, id)');
|
||||
}
|
||||
|
||||
public function down(Schema $schema): void
|
||||
{
|
||||
// this down() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('DROP TABLE "user"');
|
||||
$this->addSql('DROP TABLE messenger_messages');
|
||||
}
|
||||
}
|
||||
@@ -10,7 +10,7 @@ use Doctrine\Migrations\AbstractMigration;
|
||||
/**
|
||||
* Auto-generated Migration: Please modify to your needs!
|
||||
*/
|
||||
final class Version20260329084928 extends AbstractMigration
|
||||
final class Version20260331084216 extends AbstractMigration
|
||||
{
|
||||
public function getDescription(): string
|
||||
{
|
||||
@@ -20,8 +20,8 @@ final class Version20260329084928 extends AbstractMigration
|
||||
public function up(Schema $schema): void
|
||||
{
|
||||
// this up() migration is auto-generated, please modify it to your needs
|
||||
$this->addSql('CREATE TABLE "user" (id UUID NOT NULL, email VARCHAR(180) NOT NULL, roles JSON NOT NULL, password VARCHAR(255) NOT NULL, email_quota VARCHAR(255) NOT NULL, alternate_email VARCHAR(255) NOT NULL, identifiant_kaz VARCHAR(255) NOT NULL, quota VARCHAR(255) NOT NULL, has_nextcloud_access BOOLEAN NOT NULL, nextcloud_quota VARCHAR(255) NOT NULL, has_mobilizon BOOLEAN NOT NULL, has_agora_access BOOLEAN NOT NULL, last_name VARCHAR(255) NOT NULL, first_name VARCHAR(255) NOT NULL, photo VARCHAR(255) DEFAULT NULL, telephone VARCHAR(20) DEFAULT NULL, PRIMARY KEY (id))');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_IDENTIFIER_EMAIL ON "user" (email)');
|
||||
$this->addSql('CREATE TABLE "user" (id UUID NOT NULL, email VARCHAR(180) NOT NULL, roles JSON NOT NULL, password VARCHAR(255) NOT NULL, email_quota VARCHAR(255) NOT NULL, alternate_email VARCHAR(255) NOT NULL, identifiant_kaz VARCHAR(255) NOT NULL, quota VARCHAR(255) NOT NULL, has_nextcloud_access BOOLEAN NOT NULL, nextcloud_quota VARCHAR(255) NOT NULL, has_mobilizon BOOLEAN NOT NULL, has_agora_access BOOLEAN NOT NULL, last_name VARCHAR(255) NOT NULL, first_name VARCHAR(255) NOT NULL, image VARCHAR(255) DEFAULT NULL, telephone VARCHAR(20) DEFAULT NULL, PRIMARY KEY (id))');
|
||||
$this->addSql('CREATE UNIQUE INDEX UNIQ_8D93D649E7927C74 ON "user" (email)');
|
||||
$this->addSql('CREATE TABLE messenger_messages (id BIGINT GENERATED BY DEFAULT AS IDENTITY NOT NULL, body TEXT NOT NULL, headers TEXT NOT NULL, queue_name VARCHAR(190) NOT NULL, created_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, available_at TIMESTAMP(0) WITHOUT TIME ZONE NOT NULL, delivered_at TIMESTAMP(0) WITHOUT TIME ZONE DEFAULT NULL, PRIMARY KEY (id))');
|
||||
$this->addSql('CREATE INDEX IDX_75EA56E0FB7336F0E3BD61CE16BA31DBBF396750 ON messenger_messages (queue_name, available_at, delivered_at, id)');
|
||||
}
|
||||
@@ -7,10 +7,11 @@ use App\Form\UserProfileType;
|
||||
use App\Service\FileUploader;
|
||||
use App\Service\KazApiService;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Exception;
|
||||
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||
use Symfony\Component\Form\FormError;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
use Symfony\Component\Routing\Attribute\Route;
|
||||
@@ -37,16 +38,6 @@ class UserController extends AbstractController
|
||||
* @throws TransportExceptionInterface
|
||||
*/
|
||||
|
||||
#[Route('/user/{email}', name: 'app_user_by_mail', methods: ['GET'])]
|
||||
public function index(string $email, KazApiService $apiKazService): Response
|
||||
{
|
||||
$user = $apiKazService->getUserData($email);
|
||||
return $this->render('user/profil_infos.html.twig', [
|
||||
'user' => $user,
|
||||
]);
|
||||
}
|
||||
|
||||
/* TODO : Param l'API avec un Serializer pour la lecture du fichier JSON ? */
|
||||
#[Route('/mon-profil', name: 'app_user', methods: ['GET', 'POST'])]
|
||||
#[IsGranted('ROLE_USER')]
|
||||
public function showProfile(
|
||||
|
maurine marked this conversation as resolved
Outdated
|
||||
@@ -54,66 +45,63 @@ class UserController extends AbstractController
|
||||
EntityManagerInterface $entityManager,
|
||||
FileUploader $fileUploader,
|
||||
KazApiService $apiKazService
|
||||
): Response {
|
||||
# Récupération de l'utilisateur actuellement connecté
|
||||
): Response
|
||||
{
|
||||
// Récupération de l'utilisateur actuellement connecté
|
||||
$user = $this->getUser();
|
||||
// Vérification si l'URL est en mode édition
|
||||
$isEditMode = $request->query->getBoolean('edit', false);
|
||||
|
||||
$kazUser = $apiKazService->getUserData($user->getEmail());
|
||||
try {
|
||||
// Récupération des données de l'utilisateur sur l'API grâce à son email
|
||||
$kazUser = $apiKazService->getUserData($user->getEmail());
|
||||
// Initialisation de la variable $userData
|
||||
$user = $user->updateFromKazUser($kazUser);
|
||||
} catch (Exception $e) {
|
||||
$this->addFlash('error', 'Impossible de charger vos données.' . $e->getMessage());
|
||||
}
|
||||
|
||||
$user = $user->updateFromKazUser($kazUser);
|
||||
|
||||
//TODO: modifier pour que ça communique avec l'API */
|
||||
|
||||
# Création du formulaire lié à l'utilisateur connecté
|
||||
// Création du formulaire lié à l'utilisateur connecté
|
||||
$form = $this->createForm(UserProfileType::class, $user);
|
||||
$form->handleRequest($request);
|
||||
|
||||
# Traitement si l'utilisateur clique sur "Valider"
|
||||
// Affichage du formulaire si les données sont valides
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
|
||||
/** @var UploadedFile $imageFile */
|
||||
/** @var UploadedFile|null $imageFile */
|
||||
$imageFile = $form->get('image')->getData();
|
||||
|
||||
|
melvin-leveque marked this conversation as resolved
Outdated
melvin-leveque
commented
Le formulaire permet seulement de modifier l'image ? Le formulaire permet seulement de modifier l'image ?
maurine
commented
normalement, non. Je peux modifier l'image, le numéro de téléphone et l'adresse mail de secours normalement, non. Je peux modifier l'image, le numéro de téléphone et l'adresse mail de secours
|
||||
// --- Gestion de l'image de profil ---
|
||||
if ($imageFile) {
|
||||
# Suppression de l'ancienne photo du serveur
|
||||
$fileUploader->delete($user->getPhoto());
|
||||
|
||||
# Dépot de la nouvelle photo
|
||||
// Suppression de l'ancienne image via le service
|
||||
if ($user->getImage()) {
|
||||
$fileUploader->delete($user->getImage());
|
||||
}
|
||||
// Dépôt de la nouvelle image et mise à jour de son nom dans l'entité
|
||||
$newFilename = $fileUploader->upload($imageFile);
|
||||
|
||||
# Mise à jour de l'utilisateur avec le nouveau nom
|
||||
$user->setPhoto($newFilename);
|
||||
$user->setImage($newFilename);
|
||||
}
|
||||
// --- Fin gestion de l'image de profil ---
|
||||
|
||||
$alternateEmail = $form->get('alternateEmail')->getData();
|
||||
$regexEmail = '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/';
|
||||
if(isset($alternateEmail) && preg_match($regexEmail, $alternateEmail)) {
|
||||
$user->setAlternateEmail($form->get('alternateEmail')->getData());
|
||||
} else {
|
||||
$alternateEmail->addError(new FormError('L\'adresse e-mail n\'est pas valide.'));
|
||||
// Synchronisation des données avec l'API
|
||||
$kazUser = $user->convertToKazUser();
|
||||
|
||||
try {
|
||||
$apiKazService->updateUserData($user->getEmail(), $kazUser);
|
||||
} catch (Exception $e) {
|
||||
$this->addFlash('error', 'Impossible de mettre à jour votre profil' . $e->getMessage());
|
||||
}
|
||||
|
||||
$telephone = $form->get('telephone')->getData();
|
||||
$regexTelephone = '/^[0-9\+\s\.\-\(\)]+$/';
|
||||
if(isset($telephone) && preg_match($regexTelephone, $telephone)) {
|
||||
$user->setTelephone($telephone);
|
||||
} else {
|
||||
$telephone->addError(new FormError('Le numéro de téléphone n\'est pas valide.'));
|
||||
}
|
||||
|
||||
# Sauvegarde en base de données
|
||||
// Sauvegarde en base de données
|
||||
$entityManager->flush();
|
||||
|
||||
# Message de confirmation et rechargement de la page
|
||||
// Message de confirmation et rechargement de la page
|
||||
$this->addFlash('success', 'Votre profil a été mis à jour avec succès !');
|
||||
|
||||
// Redirection de l'utilisateur
|
||||
return $this->redirectToRoute('app_user');
|
||||
}
|
||||
|
||||
# Affichage de la page
|
||||
// Affichage de la page
|
||||
return $this->render('user/index.html.twig', [
|
||||
'form' => $form->createView(),
|
||||
'userData' => $user, # TODO : Mettre $userData quand connexion avec API OK
|
||||
'userData' => $user,
|
||||
'isEditMode' => $isEditMode,
|
||||
]);
|
||||
}
|
||||
|
||||
@@ -124,31 +112,30 @@ class UserController extends AbstractController
|
||||
EntityManagerInterface $entityManager
|
||||
): Response
|
||||
{
|
||||
# Création du formulaire
|
||||
// Création du formulaire
|
||||
$form = $this->createForm(ChangePasswordType::class);
|
||||
|
||||
# Liaison du formulaire à la requête HTTP
|
||||
// Liaison du formulaire à la requête HTTP
|
||||
$form->handleRequest($request);
|
||||
|
||||
|
melvin-leveque marked this conversation as resolved
Outdated
melvin-leveque
commented
On a deux récupération de l'utilisateur On a deux récupération de l'utilisateur `$user = $this->getUset()`
maurine
commented
je me suis trompée je me suis trompée
|
||||
# Vérification du formulaire, s'il est bien soumis et valide
|
||||
// Vérification du formulaire, s'il est bien soumis et valide
|
||||
if ($form->isSubmitted() && $form->isValid()) {
|
||||
# Récupération des données du formulaire
|
||||
// Récupération des données du formulaire
|
||||
$user = $this->getUser();
|
||||
|
melvin-leveque marked this conversation as resolved
Outdated
melvin-leveque
commented
Très bien de vérifier l'ancien mot de passe Très bien de vérifier l'ancien mot de passe
|
||||
$plainOldPassword = $form->get('oldPassword')->getData();
|
||||
$newPassword = $form->get('newPassword')->getData();
|
||||
|
||||
# Vérification de l'ancien mot de passe
|
||||
// Vérification de l'ancien mot de passe
|
||||
if (!$hasher->isPasswordValid($user, $plainOldPassword)) {
|
||||
$form->get('oldPassword')->addError(new FormError('L\'ancien mot de passe est incorrect.'));
|
||||
} else {
|
||||
# Si tout est OK : Hachage du mot de passe
|
||||
// Si tout est OK : Hachage du mot de passe
|
||||
$hashedPassword = $hasher->hashPassword($user, $newPassword);
|
||||
$user->setPassword($hashedPassword);
|
||||
|
||||
# Sauvegarde en BDD
|
||||
// Sauvegarde en BDD
|
||||
$entityManager->flush();
|
||||
|
||||
# Message de succès pour l'utilisateur
|
||||
// Message de succès pour l'utilisateur
|
||||
$this->addFlash('success', 'Votre mot de passe a bien été mis à jour !');
|
||||
|
||||
return $this->redirectToRoute('app_user_edit_password');
|
||||
|
||||
@@ -10,35 +10,36 @@ use Symfony\Component\PasswordHasher\Hasher\UserPasswordHasherInterface;
|
||||
|
||||
class AppFixtures extends Fixture
|
||||
{
|
||||
# Initialisation de l'outil de hachage de Symfony
|
||||
// Initialisation de l'outil de hachage de Symfony
|
||||
private UserPasswordHasherInterface $hasher;
|
||||
|
||||
# Injection de dépendance pour récupérer le service de sécurité
|
||||
// Injection de dépendance pour récupérer le service de sécurité
|
||||
public function __construct(UserPasswordHasherInterface $hasher)
|
||||
{
|
||||
$this->hasher = $hasher;
|
||||
}
|
||||
|
||||
# Méthode principale qui génère les données en base
|
||||
// Méthode principale qui génère des données de test en BDD locale
|
||||
public function load(ObjectManager $manager): void
|
||||
{
|
||||
# Initialisation de Faker en français
|
||||
// --- Création de 10 utilisateurs avec Faker --- //
|
||||
// Initialisation de Faker en français
|
||||
$faker = Factory::create('fr_FR');
|
||||
|
||||
# Boucle pour créer 10 utilisateurs
|
||||
// Boucle pour créer 10 utilisateurs
|
||||
|
melvin-leveque marked this conversation as resolved
Outdated
melvin-leveque
commented
Niquel les fixtures :) Niquel les fixtures :)
|
||||
for ($i = 0; $i < 10; $i++) {
|
||||
# Instanciation d'un nouvel utilisateur (Adhérent)
|
||||
// Instanciation d'un nouvel utilisateur (Adhérent)
|
||||
$user = new User();
|
||||
# Attribution d'un email aléatoire et unique
|
||||
// Attribution d'un email aléatoire et unique
|
||||
$user->setEmail($faker->unique()->safeEmail());
|
||||
# Définition des droits d'accès de l'utilisateur
|
||||
// Définition des droits d'accès de l'utilisateur
|
||||
$user->setRoles(['ROLE_USER', 'ROLE_ORGANISATION']);
|
||||
# Hachage sécurisé du mot de passe "password"
|
||||
// Hachage sécurisé du mot de passe "password"
|
||||
$user->setPassword($this->hasher->hashPassword($user, 'password'));
|
||||
# Définition d'un NOM et Prénom
|
||||
// Définition d'un NOM et Prénom
|
||||
$user->setFirstname($faker->firstName());
|
||||
$user->setLastname($faker->lastName());
|
||||
# autres fixtures à modifier plus tard
|
||||
// autres fixtures à modifier plus tard
|
||||
$user->setNextcloudQuota($faker->numberBetween(1, 20) . 'G');
|
||||
$user->setQuota($faker->numberBetween(1, 10) . 'G');
|
||||
$user->setEmailQuota('1G');
|
||||
@@ -47,7 +48,7 @@ class AppFixtures extends Fixture
|
||||
$user->setHasMobilizon($faker->boolean(50));
|
||||
$user->setHasNextcloudAccess($faker->boolean(90));
|
||||
$user->setIdentifiantKaz($faker->uuid());
|
||||
# Préparation de l'enregistrement de l'objet en base de données
|
||||
// Préparation de l'enregistrement de l'objet en base de données
|
||||
$manager->persist($user);
|
||||
}
|
||||
|
||||
@@ -56,10 +57,9 @@ class AppFixtures extends Fixture
|
||||
$admin->setEmail('admin@kaz.bzh');
|
||||
$admin->setRoles(['ROLE_USER', 'ROLE_ADMIN', 'ROLE_ORGANISATION']);
|
||||
$admin->setPassword($this->hasher->hashPassword($admin, 'password'));
|
||||
// Remplissage des champs obligatoires restants pour éviter les erreurs SQL
|
||||
$admin->setFirstName('Admin');
|
||||
$admin->setLastName('KAZ');
|
||||
|
||||
// Remplissage des champs obligatoires restants pour éviter les erreurs SQL
|
||||
$admin->setAlternateEmail('secours@kaz.bzh');
|
||||
$admin->setIdentifiantKaz('ADMIN-KAZ-001');
|
||||
$admin->setQuota('5G');
|
||||
@@ -71,14 +71,13 @@ class AppFixtures extends Fixture
|
||||
|
||||
$manager->persist($admin);
|
||||
|
||||
// Création d'un compte de test fixe
|
||||
// Création d'un compte de test fixe présent dans le LDAP
|
||||
$melvin = new User();
|
||||
$melvin->setEmail('melvin.leveque@kazkouil.fr');
|
||||
$melvin->setRoles(['ROLE_USER', 'ROLE_ADMIN', 'ROLE_ORGANISATION']);
|
||||
$melvin->setPassword($this->hasher->hashPassword($melvin, 'password'));
|
||||
$melvin->setFirstName('');
|
||||
$melvin->setLastName('');
|
||||
|
||||
$melvin->setAlternateEmail('');
|
||||
$melvin->setIdentifiantKaz('MELVIN-KAZ-001');
|
||||
$melvin->setQuota('5G');
|
||||
@@ -90,7 +89,24 @@ class AppFixtures extends Fixture
|
||||
|
||||
$manager->persist($melvin);
|
||||
|
||||
# Exécution réelle des requêtes SQL (envoi vers la base), une fois la bouche finie
|
||||
// Création d'un compte de test fixe présent dans le LDAP pour ma présentation
|
||||
$toto = new User();
|
||||
$toto->setEmail('toto@kazkouil.fr');
|
||||
$toto->setRoles(['ROLE_USER', 'ROLE_ADMIN', 'ROLE_ORGANISATION']);
|
||||
$toto->setPassword($this->hasher->hashPassword($toto, 'password'));
|
||||
$toto->setFirstName('');
|
||||
$toto->setLastName('');
|
||||
$toto->setAlternateEmail('');
|
||||
$toto->setIdentifiantKaz('');
|
||||
$toto->setQuota('5G');
|
||||
$toto->setEmailQuota('1G');
|
||||
$toto->setNextcloudQuota('10G');
|
||||
$toto->setHasNextcloudAccess(true);
|
||||
$toto->setHasMobilizon(true);
|
||||
$toto->setHasAgoraAccess(true);
|
||||
$manager->persist($toto);
|
||||
|
||||
// Exécution réelle des requêtes SQL (envoi vers la base), une fois la bouche finie
|
||||
$manager->flush();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,6 @@
|
||||
namespace App\Entity;
|
||||
|
||||
use App\Repository\UserRepository;
|
||||
use Doctrine\DBAL\Types\Types;
|
||||
use Doctrine\ORM\Mapping as ORM;
|
||||
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
|
||||
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
|
||||
@@ -69,13 +68,18 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||
#[ORM\Column(length: 255, name: 'first_name')]
|
||||
private ?string $firstName = null;
|
||||
|
||||
// TODO: Modifier "photo" par "image"
|
||||
#[ORM\Column(length: 255, nullable: true, name: 'photo')]
|
||||
private ?string $photo = null;
|
||||
#[ORM\Column(length: 255, nullable: true, name: 'image')]
|
||||
private ?string $image = null;
|
||||
|
||||
#[ORM\Column(length: 20, nullable: true, name: 'telephone')]
|
||||
private ?string $telephone = null;
|
||||
|
||||
private ?string $numeroMembre = null;
|
||||
|
||||
private ?bool $mailEnabled = null;
|
||||
|
||||
private ?string $mailAlias = null;
|
||||
|
||||
public function __construct() {
|
||||
$this->emailQuota = self::EMAIL_QUOTA_DEFAULT;
|
||||
}
|
||||
@@ -290,14 +294,14 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPhoto(): ?string
|
||||
public function getImage(): ?string
|
||||
{
|
||||
return $this->photo;
|
||||
return $this->image;
|
||||
}
|
||||
|
||||
public function setPhoto(?string $photo): static
|
||||
public function setImage(?string $image): static
|
||||
{
|
||||
$this->photo = $photo;
|
||||
$this->image = $image;
|
||||
|
||||
return $this;
|
||||
}
|
||||
@@ -314,18 +318,80 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getNumeroMembre(): ?string
|
||||
{
|
||||
return $this->numeroMembre;
|
||||
}
|
||||
|
||||
public function setNumeroMembre(?string $numeroMembre): static
|
||||
{
|
||||
$this->numeroMembre = $numeroMembre;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function isMailEnabled(): ?bool
|
||||
{
|
||||
return $this->mailEnabled;
|
||||
}
|
||||
|
||||
public function setMailEnabled(?bool $mailEnabled): static
|
||||
{
|
||||
$this->mailEnabled = $mailEnabled;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getMailAlias(): ?string
|
||||
{
|
||||
return $this->mailAlias;
|
||||
}
|
||||
|
||||
public function setMailAlias(?string $mailAlias): static
|
||||
{
|
||||
$this->mailAlias = $mailAlias;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Fonction qui permet d'afficher les données de l'API sur la page de profil
|
||||
public function updateFromKazUser($kazUser) : User
|
||||
{
|
||||
// Récupération et conversion des données de l'API pour les afficher
|
||||
$this->setEmail($kazUser['mail']);
|
||||
// Création du firstname et lastname
|
||||
// Création du firstname et lastname (une seule donnée sur l'API)
|
||||
$name = explode(' ', $kazUser['sn']);
|
||||
$this->setFirstName($name[0]);
|
||||
// Récupération des valeurs du tableau moins la première
|
||||
$aLastname = array_slice($name, 1);
|
||||
$this->setLastName(implode(' ', $aLastname));
|
||||
|
||||
//TODO: Ajouter les champs manquants de l'objet User dans l'api kaz.
|
||||
|
||||
// Récupération du mail de secours
|
||||
$this->setAlternateEmail($kazUser['mailDeSecours']);
|
||||
$this->setEmailQuota($kazUser['mailQuota']);
|
||||
$this->setHasAgoraAccess($kazUser['agoraEnabled']);
|
||||
$this->setHasMobilizon($kazUser['mobilizonEnabled']);
|
||||
$this->setHasNextcloudAccess($kazUser['nextcloudEnabled']);
|
||||
$this->setNextcloudQuota($kazUser['nextcloudQuota']);
|
||||
$this->setQuota($kazUser['quota']);
|
||||
$this->setIdentifiantKaz($kazUser['identifiantKaz']);
|
||||
return $this;
|
||||
}
|
||||
|
||||
// Fonction qui permet de convertir les données de l'API vers $kazUser
|
||||
public function convertToKazUser() : array
|
||||
{
|
||||
$data = [
|
||||
'numeroMembre' => $this->getNumeroMembre(),
|
||||
'mailDeSecours' => $this->getAlternateEmail(),
|
||||
'mailEnabled' => $this->isMailEnabled(),
|
||||
'nextcloudEnabled' => $this->hasNextcloudAccess(),
|
||||
'mobilizonEnabled' => $this->hasMobilizon(),
|
||||
'agoraEnabled' => $this->hasAgoraAccess(),
|
||||
'identifiantKaz' => $this->getIdentifiantKaz(),
|
||||
'mailAlias' => $this->getMailAlias(),
|
||||
'quota' => $this->getQuota(),
|
||||
];
|
||||
|
||||
return array_filter($data, fn($value) => $value !== null);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
namespace App\Form;
|
||||
|
||||
use App\Entity\User;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TelType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\AbstractType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\CheckboxType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\EmailType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\FileType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TelType;
|
||||
use Symfony\Component\Form\Extension\Core\Type\TextType;
|
||||
use Symfony\Component\Form\FormBuilderInterface;
|
||||
use Symfony\Component\OptionsResolver\OptionsResolver;
|
||||
use Symfony\Component\Validator\Constraints\Image;
|
||||
@@ -27,22 +28,53 @@ class UserProfileType extends AbstractType
|
||||
'label' => 'Nom',
|
||||
'disabled' => true,
|
||||
])
|
||||
->add('identifiantKaz', TextType::class, [
|
||||
'label' => 'Identifiant KAZ : ',
|
||||
'disabled' => true,
|
||||
])
|
||||
->add('email', EmailType::class, [
|
||||
'label' => 'E-mail',
|
||||
'disabled' => true,
|
||||
])
|
||||
->add('alternateEmail', EmailType::class, ['label' => 'E-mail de secours'])
|
||||
->add('alternateEmail', EmailType::class, [
|
||||
'label' => 'E-mail de secours',
|
||||
'constraints' => [
|
||||
new Regex(
|
||||
pattern: '/^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/',
|
||||
message: 'L\'adresse e-mail n\'est pas valide.'
|
||||
)
|
||||
]
|
||||
])
|
||||
->add('emailQuota', TextType::class, [
|
||||
'label' => 'Espace de stockage de votre boîte mail : ',
|
||||
'disabled' => true,
|
||||
])
|
||||
->add('hasNextcloudAccess', CheckboxType::class, [
|
||||
'label' => 'Accès au Nextcloud : ',
|
||||
'disabled' => true,
|
||||
])
|
||||
->add('nextcloudQuota', TextType::class, [
|
||||
'label' => 'Espace de stockage de votre Nextcloud : ',
|
||||
'disabled' => true,
|
||||
])
|
||||
->add('hasMobilizon', CheckboxType::class, [
|
||||
'label' => 'Accès à Mobilizon : ',
|
||||
'disabled' => true,
|
||||
])
|
||||
->add('hasAgoraAccess', CheckboxType::class, [
|
||||
'label' => 'Accès à l\'Agora : ',
|
||||
'disabled' => true,
|
||||
])
|
||||
->add('telephone', TelType::class, [
|
||||
'label'=>'Téléphone',
|
||||
'required' => false,
|
||||
'attr' => [
|
||||
'placeholder'=>'06 00 00 00 00',
|
||||
'class'=> 'w-full px-4 py-3 border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton transition-shadow'
|
||||
],
|
||||
'constraints' => [
|
||||
new Regex(
|
||||
pattern: '/^[0-9\+\s\.\-\(\)]+$/',
|
||||
message: 'Le numéro de téléphone contient des caractères non valides'
|
||||
message: 'Le numéro de téléphone n\'est pas valide.'
|
||||
),
|
||||
new Length(
|
||||
max: 20,
|
||||
@@ -51,14 +83,14 @@ class UserProfileType extends AbstractType
|
||||
],
|
||||
])
|
||||
->add('image', FileType::class, [
|
||||
'label' => 'Ma photo de profil',
|
||||
'label' => 'Mon image de profil',
|
||||
'mapped' => false,
|
||||
'required' => false,
|
||||
'constraints' => [
|
||||
new Image(
|
||||
maxSize: '2M',
|
||||
extensions: ['jpg', 'jpeg', 'png'],
|
||||
extensionsMessage: 'Veuillez déposer une image JPG, JPEG ou PNG valide',)
|
||||
maxSize: '8M',
|
||||
extensions: ['jpg', 'jpeg', 'png', 'gif'],
|
||||
extensionsMessage: 'Veuillez déposer une image JPG, JPEG, GIF ou PNG valide')
|
||||
],
|
||||
])
|
||||
;
|
||||
|
||||
@@ -2,45 +2,76 @@
|
||||
|
||||
namespace App\Service;
|
||||
|
||||
use RuntimeException;
|
||||
use Symfony\Component\DependencyInjection\Attribute\Autowire;
|
||||
use Symfony\Component\HttpFoundation\File\Exception\FileException;
|
||||
use Symfony\Component\HttpFoundation\File\UploadedFile;
|
||||
use Symfony\Component\String\Slugger\SluggerInterface;
|
||||
|
||||
/**
|
||||
* Service de gestion des envois et suppressions de fichiers.
|
||||
*/
|
||||
class FileUploader
|
||||
{
|
||||
// On utilise la promotion de constructeur (PHP 8) : ultra moderne et concis
|
||||
/**
|
||||
* @param string $targetDirectory Le chemin absolu vers le dossier de dépôt.
|
||||
* @param SluggerInterface $slugger Le service de nettoyage des chaînes de caractères.
|
||||
*/
|
||||
public function __construct(
|
||||
private string $targetDirectory,
|
||||
private SluggerInterface $slugger,
|
||||
) {
|
||||
#[Autowire('%kernel.project_dir%/public/uploads/img')]
|
||||
private readonly string $targetDirectory,
|
||||
private readonly SluggerInterface $slugger,
|
||||
)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Traite, sécurise et déplace un fichier déposé.
|
||||
*
|
||||
* @param UploadedFile $file Le fichier physique à déposer.
|
||||
* @return string Le nom final sécurisé et unique du fichier.
|
||||
* @throws RuntimeException En cas d'échec de l'écriture sur le disque.
|
||||
*/
|
||||
public function upload(UploadedFile $file): string
|
||||
{
|
||||
$originalFilename = pathinfo($file->getClientOriginalName(), PATHINFO_FILENAME);
|
||||
$safeFilename = $this->slugger->slug($originalFilename);
|
||||
$fileName = $safeFilename . '-' . uniqid() . '.' . $file->guessExtension();
|
||||
|
||||
// Utilisation de uniqid('', true) pour garantir une unicité absolue en production
|
||||
$fileName = sprintf('%s-%s.%s', $safeFilename, uniqid('', true), $file->guessExtension());
|
||||
|
||||
try {
|
||||
$file->move($this->getTargetDirectory(), $fileName);
|
||||
} catch (FileException $e) {
|
||||
// Ici tu peux logguer l'erreur si besoin
|
||||
throw new \Exception('Erreur lors du transfert de l\'image : ' . $e->getMessage());
|
||||
throw new RuntimeException('Erreur lors du transfert de l\'image : ' . $e->getMessage(), 0, $e);
|
||||
}
|
||||
|
||||
return $fileName;
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime physiquement un fichier du serveur.
|
||||
*
|
||||
* @param string|null $fileName Le nom du fichier à supprimer.
|
||||
*/
|
||||
public function delete(?string $fileName): void
|
||||
{
|
||||
if ($fileName) {
|
||||
$filePath = $this->getTargetDirectory() . 'FileUploader.php/' . $fileName;
|
||||
if (file_exists($filePath)) {
|
||||
unlink($filePath);
|
||||
}
|
||||
if (null === $fileName) {
|
||||
return;
|
||||
}
|
||||
|
||||
$filePath = rtrim($this->getTargetDirectory(), '/') . '/' . $fileName;
|
||||
|
||||
if (file_exists($filePath)) {
|
||||
unlink($filePath);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne le chemin du répertoire de dépôt.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function getTargetDirectory(): string
|
||||
{
|
||||
return $this->targetDirectory;
|
||||
|
||||
@@ -4,6 +4,8 @@ namespace App\Service;
|
||||
|
||||
|
||||
use Exception;
|
||||
use Symfony\Component\Mime\Part\DataPart;
|
||||
use Symfony\Component\Mime\Part\Multipart\FormDataPart;
|
||||
use Symfony\Contracts\HttpClient\Exception\ClientExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\DecodingExceptionInterface;
|
||||
use Symfony\Contracts\HttpClient\Exception\RedirectionExceptionInterface;
|
||||
@@ -50,7 +52,7 @@ class KazApiService
|
||||
}
|
||||
|
||||
$data = $response->toArray();
|
||||
$this->token = $data['access_token']; // Ajustez la clé selon le format de votre API
|
||||
$this->token = $data['access_token'];
|
||||
|
||||
return $this->token;
|
||||
}
|
||||
@@ -80,4 +82,26 @@ class KazApiService
|
||||
|
||||
return $response->toArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Envoie les nouvelles données saisies par l'utilisateur vers l'API
|
||||
*
|
||||
* @throws ClientExceptionInterface
|
||||
* @throws DecodingExceptionInterface
|
||||
* @throws RedirectionExceptionInterface
|
||||
* @throws ServerExceptionInterface
|
||||
* @throws TransportExceptionInterface
|
||||
* @throws Exception
|
||||
*/
|
||||
public function updateUserData(string $email, array $kazUser): void
|
||||
{
|
||||
$options['headers']['Authorization'] = 'Bearer ' . $this->getToken();
|
||||
$options['headers']['Content-Type'] = 'application/json';
|
||||
$options['json'] = $kazUser;
|
||||
$response = $this->kazApiClient->request('PATCH', "/ldap/user/update/$email", $options);
|
||||
|
||||
if ($response->getStatusCode() !== 200) {
|
||||
throw new Exception('Erreur lors de l\'appel API : ' . $response->getStatusCode());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,13 +80,13 @@
|
||||
|
||||
<ul class="flex flex-wrap justify-center gap-4 sm:gap-6 text-sm text-gris-fonce">
|
||||
<li>
|
||||
<a href="https://kaz.bzh/mentions-legales/" class="hover:text-bouton transition-colors">Mentions légales et statuts</a>
|
||||
<a href="https://kaz.bzh/mentions-legales/" target="_blank" class="hover:text-bouton transition-colors">Mentions légales et statuts</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://status.kaz.bzh/status/kaz" class="hover:text-bouton transition-colors">Santé des services Kaz</a>
|
||||
<a href="https://status.kaz.bzh/status/kaz" target="_blank" class="hover:text-bouton transition-colors">Santé des services Kaz</a>
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://kaz.bzh/contact/" class="hover:text-bouton transition-colors">Contact</a>
|
||||
<a href="https://kaz.bzh/contact/" target="_blank" class="hover:text-bouton transition-colors">Contact</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
@@ -16,8 +16,8 @@
|
||||
Bienvenue sur ton espace kaznaute <span class="text-bouton">{{ app.user ? app.user.userIdentifier : 'visiteur' }}</span> !
|
||||
</h2>
|
||||
|
||||
{# Zone réservée pour les futures données de l'API
|
||||
TODO : Gérer les données de l'API #}
|
||||
{# Zone réservée pour les futures données de Pahéko
|
||||
TODO : Gérer les données avec Pahéko, mise en service en cours par un des développeurs de l'association. Cela sera vu à posteriori du stage #}
|
||||
|
melvin-leveque marked this conversation as resolved
Outdated
melvin-leveque
commented
Bonne pratique de mettre les TODO dans le code :) Bonne pratique de mettre les TODO dans le code :)
|
||||
<div class="bg-bouton/10 border border-bouton/30 rounded-lg p-5">
|
||||
<h3 class="font-semibold text-title mb-3 flex items-center gap-2">
|
||||
Votre abonnement actuellement :
|
||||
@@ -91,7 +91,7 @@
|
||||
</div>
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="https://kaz.bzh/contact/" class="group flex items-center p-5 bg-white border border-gris-clair rounded-xl shadow-sm hover:shadow-md hover:border-bouton transition-all duration-200">
|
||||
<a href="https://kaz.bzh/contact/" target="_blank" class="group flex items-center p-5 bg-white border border-gris-clair rounded-xl shadow-sm hover:shadow-md hover:border-bouton transition-all duration-200">
|
||||
<div class="flex-shrink-0 bg-purple-50 text-purple-600 rounded-lg p-3 group-hover:bg-purple-600 group-hover:text-white transition-colors">
|
||||
<span class="text-2xl block">✉️</span>
|
||||
</div>
|
||||
|
||||
@@ -62,10 +62,12 @@
|
||||
</div>
|
||||
|
||||
{# Bouton de validation #}
|
||||
<div class="md:col-span-3 w-full flex justify-center pt-8 pb-4">
|
||||
<button type="submit"
|
||||
class="w-full bg-bouton hover:bg-bouton-hover text-text font-bold py-3 rounded-lg shadow-md transition-colors mt-6">
|
||||
class="px-8 py-2.5 bg-bouton hover:bg-bouton-hover text-text text-sm font-bold rounded-lg shadow transition-colors">
|
||||
Mettre à jour mon mot de passe
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
{{ form_end(form) }}
|
||||
|
||||
|
||||
@@ -1,135 +1,283 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Accueil | {{ parent() }}{% endblock %}
|
||||
{# @var userData \App\Entity\User #}
|
||||
{# @var form \Symfony\Component\Form\FormView #}
|
||||
{# @var isEditMode bool #}
|
||||
|
||||
{% block title %}Ma page de profil | {{ parent() }}{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="min-h-screen bg-bg-primaire py-8 w-full font-sora">
|
||||
|
melvin-leveque marked this conversation as resolved
Outdated
melvin-leveque
commented
On peut aussi avoir l'information de la photo seulement au niveau de l'app symfony :) On peut aussi avoir l'information de la photo seulement au niveau de l'app symfony :)
C'est toujours sympa d'avoir de la personnalisation pour les utilisateurs
maurine
commented
d'ac ! d'ac !
|
||||
{# Affichage du formulaire (seulement en mode édition) #}
|
||||
<div class="max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 grid md:grid-cols-3 gap-8">
|
||||
{% if isEditMode %}
|
||||
{{ form_start(form, {'attr': {'class': 'contents'}}) }}
|
||||
{% endif %}
|
||||
|
||||
{{ form_start(form, {'attr': {'class': 'max-w-6xl mx-auto px-4 sm:px-6 lg:px-8 grid md:grid-cols-3 gap-8'}}) }}
|
||||
{# Colonne Photo #}
|
||||
<div class="flex flex-col text-text items-center">
|
||||
|
||||
{# TODO: voir si c'est pertinent avec l'API et s'il y a l'utilité d'une photo de profil #}
|
||||
{# Gestion de la colone avec le choix de la photo de profil #}
|
||||
<div class="flex flex-col text-text items-center">
|
||||
|
||||
{# Affichage de la photo de profil #}
|
||||
<div class="w-full md:w-64 flex-shrink-0 mt-20">
|
||||
<div class="border-2 border-black p-1 bg-white shadow-[8px_8px_0px_0px_rgba(0,0,0,1)]">
|
||||
{% if userData.photo %}
|
||||
<img src="{{ asset('uploads/images/' ~ userData.photo) }}"
|
||||
<div class="mt-16 flex-shrink-0">
|
||||
{% if userData.image %}
|
||||
{# Design avec photo #}
|
||||
<img src="{{ asset('uploads/images/' ~ userData.image) }}"
|
||||
alt="Photo de profil"
|
||||
class="w-full aspect-[4/3] object-cover">
|
||||
class="w-48 h-48 md:w-56 md:h-56 p-1 rounded-full ring-2 ring-gris-clair object-cover">
|
||||
{% else %}
|
||||
<div class="w-full aspect-[4/3] bg-gray-50 flex items-center justify-center text-6xl">
|
||||
👤
|
||||
{# Design sans photo #}
|
||||
<div
|
||||
class="relative w-48 h-48 md:w-56 md:h-56 overflow-hidden bg-neutral-200 rounded-full flex items-end justify-center shadow-sm">
|
||||
<svg class="w-5/6 h-5/6 text-neutral-500" fill="currentColor" viewBox="0 0 20 20"
|
||||
xmlns="http://www.w3.org/2000/svg">
|
||||
<path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"
|
||||
clip-rule="evenodd"></path>
|
||||
</svg>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<p class="text-2xl text-title font-caveat mt-6"> Ma photo</p>
|
||||
|
||||
{# Gestion du dépôt d'un fichier image (Uniquement en mode édition) #}
|
||||
{% if isEditMode %}
|
||||
<div class="w-full mt-4">
|
||||
{{ form_label(form.image, 'Choisir un fichier', {
|
||||
'label_attr': {'class': 'block mb-2.5 text-sm font-medium text-gris-fonce'}
|
||||
}) }}
|
||||
{{ form_widget(form.image, {
|
||||
'attr': {
|
||||
'class': 'cursor-pointer bg-white border border-gris-clair text-text text-sm rounded-lg focus:outline-none focus:ring-1 focus:ring-bouton focus:border-bouton block w-full shadow-sm placeholder-gris-moyen file:mr-4 file:py-2.5 file:px-4 file:border-0 file:border-r file:border-gris-clair file:bg-gris-clair file:text-gris-fonce hover:file:bg-gris-moyen transition-colors',
|
||||
'aria-describedby': 'file_input_help'
|
||||
}
|
||||
}) }}
|
||||
<p class="mt-1 text-sm text-gris-moyen" id="file_input_help">
|
||||
JPG, JPEG, GIF ou PNG (Taille max : 8Mo).
|
||||
</p>
|
||||
<div class="text-red-500 text-xs mt-1 italic font-sora">
|
||||
{{ form_errors(form.image) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# Colonne Infos Persos #}
|
||||
<div class="md:col-span-2">
|
||||
|
||||
{# Gestion des boutons d'action (Modifier / Annuler) et des titres #}
|
||||
<div class="relative flex items-center justify-center mb-6">
|
||||
<h1 class="text-4xl font-caveat text-text m-0">Mon profil</h1>
|
||||
|
||||
<div class="absolute right-0">
|
||||
{% if isEditMode %}
|
||||
<a href="{{ path('app_user') }}"
|
||||
class="flex items-center gap-1.5 px-3 py-1.5 bg-white border border-gris-clair hover:bg-gray-50 text-text text-sm font-bold rounded-md shadow-sm transition-colors">
|
||||
❌ Annuler
|
||||
</a>
|
||||
{% else %}
|
||||
<a href="{{ path('app_user', {'edit': 1}) }}"
|
||||
class="flex items-center gap-1.5 px-3 py-1.5 bg-white border border-gris-clair hover:bg-gray-50 text-text text-sm font-bold rounded-md shadow-sm transition-colors">
|
||||
✏️ Modifier
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h2 class="text-2xl font-caveat text-text mb-6 text-center">Mes informations personnelles</h2>
|
||||
|
||||
{# --- Gestion de l'affichage des informations personnelles --- #}
|
||||
<div class="flex flex-col gap-6">
|
||||
{# Identifiant #}
|
||||
<div class="space-y-1">
|
||||
<label class="block text-base font-semibold text-text">
|
||||
Identifiant Kaz :
|
||||
</label>
|
||||
{% if isEditMode %}
|
||||
{{ form_widget(form.identifiantKaz, {'attr': {'class': 'w-full px-4 py-3 text-base bg-gray-100 border border-gris-clair rounded-lg text-gray-500 cursor-not-allowed'}}) }}
|
||||
{% else %}
|
||||
<div
|
||||
class="w-full px-4 py-3 text-base bg-gray-50 border border-gris-clair rounded-lg text-text">
|
||||
{{ userData.identifiantKaz ?? 'Non défini' }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# --- NOM et Prénom --- #}
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
{# NOM #}
|
||||
<div class="space-y-1">
|
||||
<label class="block text-base font-semibold text-text">
|
||||
NOM :
|
||||
</label>
|
||||
{% if isEditMode %}
|
||||
{{ form_widget(form.lastName, {'attr': {'class': 'w-full px-4 py-3 text-base bg-gray-100 border border-gris-clair rounded-lg text-gray-500 cursor-not-allowed'}}) }}
|
||||
<div class="text-red-500 text-xs mt-1 italic font-sora">
|
||||
{{ form_errors(form.lastName) }}
|
||||
</div>
|
||||
{% else %}
|
||||
<div
|
||||
class="w-full px-4 py-3 text-base bg-gray-50 border border-gris-clair rounded-lg text-text">
|
||||
{{ userData.lastName }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# Prénom #}
|
||||
<div class="space-y-1">
|
||||
<label class="block text-base font-semibold text-text">
|
||||
Prénom :
|
||||
</label>
|
||||
{% if isEditMode %}
|
||||
{{ form_widget(form.firstName, {'attr': {'class': 'w-full px-4 py-3 text-base bg-gray-100 border border-gris-clair rounded-lg text-gray-500 cursor-not-allowed'}}) }}
|
||||
<div class="text-red-500 text-xs mt-1 italic font-sora">
|
||||
{{ form_errors(form.firstName) }}
|
||||
</div>
|
||||
{% else %}
|
||||
<div
|
||||
class="w-full px-4 py-3 text-base bg-gray-50 border border-gris-clair rounded-lg text-text">
|
||||
{{ userData.firstName }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Téléphone #}
|
||||
<div class="space-y-1">
|
||||
<label class="block text-base font-semibold text-text">
|
||||
Numéro de téléphone :
|
||||
</label>
|
||||
{% if isEditMode %}
|
||||
{{ form_widget(form.telephone, {'attr': {'class': 'w-full px-4 py-3 text-base bg-white border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton placeholder-gris-moyen transition-shadow'}}) }}
|
||||
<div class="text-red-500 text-xs mt-1 italic">
|
||||
{{ form_errors(form.telephone) }}
|
||||
</div>
|
||||
{% else %}
|
||||
<div
|
||||
class="w-full px-4 py-3 text-base bg-gray-50 border border-gris-clair rounded-lg text-text">
|
||||
{{ userData.telephone ?? 'Non renseigné' }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# E-mail #}
|
||||
<div class="space-y-1">
|
||||
<label class="block text-base font-semibold text-text">
|
||||
E-mail :
|
||||
</label>
|
||||
{% if isEditMode %}
|
||||
{{ form_widget(form.email, {'attr': {'class': 'w-full px-4 py-3 text-base bg-gray-100 border border-gris-clair rounded-lg text-gray-500 cursor-not-allowed'}}) }}
|
||||
<div class="text-red-500 text-xs mt-1 italic font-sora">
|
||||
{{ form_errors(form.email) }}
|
||||
</div>
|
||||
{% else %}
|
||||
<div
|
||||
class="w-full px-4 py-3 text-base bg-gray-50 border border-gris-clair rounded-lg text-text">
|
||||
{{ userData.email }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# E-mail de secours #}
|
||||
<div class="space-y-1">
|
||||
<label class="block text-base font-semibold text-text">
|
||||
E-mail de secours :
|
||||
</label>
|
||||
{% if isEditMode %}
|
||||
{{ form_widget(form.alternateEmail, {'attr': {'class': 'w-full px-4 py-3 text-base bg-white border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton placeholder-gris-moyen transition-shadow'}}) }}
|
||||
<div class="text-red-500 text-xs mt-1 italic font-sora">
|
||||
{{ form_errors(form.alternateEmail) }}
|
||||
</div>
|
||||
{% else %}
|
||||
<div
|
||||
class="w-full px-4 py-3 text-base bg-gray-50 border border-gris-clair rounded-lg text-text">
|
||||
{{ userData.alternateEmail ?? 'Non renseigné' }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# Quota Email #}
|
||||
<div class="space-y-1">
|
||||
<label class="block text-base font-semibold text-text">
|
||||
Espace disponible dans votre boîte mail :
|
||||
</label>
|
||||
{% if isEditMode %}
|
||||
{{ form_widget(form.emailQuota, {'attr': {'class': 'w-full px-4 py-3 text-base bg-gray-100 border border-gris-clair rounded-lg text-gray-500 cursor-not-allowed'}}) }}
|
||||
{% else %}
|
||||
<div
|
||||
class="w-full px-4 py-3 text-base bg-gray-50 border border-gris-clair rounded-lg text-text">
|
||||
{{ userData.emailQuota ?? 'Non défini' }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# Quota Nextcloud #}
|
||||
<div class="space-y-1 sm:col-span-2">
|
||||
<label class="block text-base font-semibold text-text">
|
||||
Quota Nextcloud :
|
||||
</label>
|
||||
{% if isEditMode %}
|
||||
{{ form_widget(form.nextcloudQuota, {'attr': {'class': 'w-full px-4 py-3 text-base bg-gray-100 border border-gris-clair rounded-lg text-gray-500 cursor-not-allowed'}}) }}
|
||||
{% else %}
|
||||
<div
|
||||
class="w-full px-4 py-3 text-base bg-gray-50 border border-gris-clair rounded-lg text-text">
|
||||
{{ userData.nextcloudQuota ?? 'Non défini' }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# --- Gestion de l'affichage des checkbox des différents accès --- #}
|
||||
<div class="sm:col-span-2 grid grid-cols-1 sm:grid-cols-3 gap-4 pt-4 border-t border-gris-clair">
|
||||
|
||||
{# Accès Nextcloud #}
|
||||
<div class="flex items-center gap-3">
|
||||
{% if isEditMode %}
|
||||
{{ form_widget(form.hasNextcloudAccess, {'attr': {'class': 'w-5 h-5 text-bouton border-gris-clair rounded focus:ring-bouton cursor-not-allowed opacity-60'}}) }}
|
||||
{{ form_label(form.hasNextcloudAccess, null, {'label_attr': {'class': 'text-base font-medium text-text'}}) }}
|
||||
{% else %}
|
||||
<span class="text-l">{% if userData.hasNextcloudAccess %}✅{% else %}❌{% endif %}</span>
|
||||
<span class="text-base font-medium text-text">Accès Nextcloud</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# Accès Mobilizon #}
|
||||
<div class="flex items-center gap-3">
|
||||
{% if isEditMode %}
|
||||
{{ form_widget(form.hasMobilizon, {'attr': {'class': 'w-5 h-5 text-bouton border-gris-clair rounded focus:ring-bouton cursor-not-allowed opacity-60'}}) }}
|
||||
{{ form_label(form.hasMobilizon, null, {'label_attr': {'class': 'text-base font-medium text-text'}}) }}
|
||||
{% else %}
|
||||
<span class="text-l">{% if userData.hasMobilizon %}✅{% else %}❌{% endif %}</span>
|
||||
<span class="text-base font-medium text-text">Accès Mobilizon</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{# Accès Agora #}
|
||||
<div class="flex items-center gap-3">
|
||||
{% if isEditMode %}
|
||||
{{ form_widget(form.hasAgoraAccess, {'attr': {'class': 'w-5 h-5 text-bouton border-gris-clair rounded focus:ring-bouton cursor-not-allowed opacity-60'}}) }}
|
||||
{{ form_label(form.hasAgoraAccess, null, {'label_attr': {'class': 'text-base font-medium text-text'}}) }}
|
||||
{% else %}
|
||||
<span class="text-l">{% if userData.hasAgoraAccess %}✅{% else %}❌{% endif %}</span>
|
||||
<span class="text-base font-medium text-text">Accès Agora</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Affichage du bouton "Valider" (seulement en mode édition) #}
|
||||
{% if isEditMode %}
|
||||
<div class="flex justify-center pt-8 pb-4">
|
||||
<button type="submit"
|
||||
class="px-8 py-2.5 bg-bouton hover:bg-bouton-hover text-text text-sm font-bold rounded-lg shadow transition-colors">
|
||||
Enregistrer les modifications
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p class="text-2xl text-title font-caveat mt-4"> Ma photo</p>
|
||||
{# Fermuture du formulaire (seulement en mode édition) #}
|
||||
{% if isEditMode %}
|
||||
{{ form_end(form) }}
|
||||
{% endif %}
|
||||
|
||||
{# Gestion du dépôt d'un fichier image #}
|
||||
<div class="w-full mt-2">
|
||||
{{ form_label(form.image, 'Choisir un fichier', {
|
||||
'label_attr': {'class': 'block mb-2.5 text-sm font-medium text-gris-fonce'}
|
||||
}) }}
|
||||
{{ form_widget(form.image, {
|
||||
'attr': {
|
||||
'class': 'cursor-pointer bg-white border border-gris-clair text-text text-sm rounded-lg focus:outline-none focus:ring-1 focus:ring-bouton focus:border-bouton block w-full shadow-sm placeholder-gris-moyen file:mr-4 file:py-2.5 file:px-4 file:border-0 file:border-r file:border-gris-clair file:bg-gris-clair file:text-gris-fonce hover:file:bg-gris-moyen file:cursor-pointer transition-colors',
|
||||
'aria-describedby': 'file_input_help'
|
||||
}
|
||||
}) }}
|
||||
<p class="mt-1 text-sm text-gris-moyen" id="file_input_help">
|
||||
JPG, JPEG ou PNG (Taille max : 2Mo).
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Gestion de la colonne avec les "infos persos" #}
|
||||
<div class="md:col-span-2">
|
||||
<h1 class="text-4xl font-caveat text-text mb-6 text-center sm:text-center">Mon profil</h1>
|
||||
<h2 class="text-2xl font-caveat text-text mb-6 text-center sm:text-center">Mes informations personnelles</h2>
|
||||
|
||||
{# Gestion du formulaire qui regroupe toutes les infos perso #}
|
||||
<div class="flex flex-col gap-6">
|
||||
|
||||
{# Champ NOM et Prénom #}
|
||||
<div class="grid grid-cols-2 gap-4">
|
||||
<div class="space-y-1">
|
||||
{{ form_label(form.firstName, 'NOM :', {
|
||||
'label_attr': {'class': 'block text-sm font-semibold text-text'}
|
||||
}) }}
|
||||
{{ form_widget(form.firstName, {
|
||||
'attr': {'class': 'w-full px-4 py-3 border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton placeholder-gris-moyen transition-shadow'}
|
||||
}) }}
|
||||
{# Implémentation d'un message d'errer en cas de problème #}
|
||||
<div class="text-red-500 text-xs mt-1 italic font-sora">
|
||||
{{ form_errors(form.firstName) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="space-y-1">
|
||||
{{ form_label(form.lastName, 'Prénom :', {
|
||||
'label_attr': {'class': 'block text-sm font-semibold text-text'}
|
||||
}) }}
|
||||
{{ form_widget(form.lastName, {
|
||||
'attr': {'class': 'w-full px-4 py-3 border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton placeholder-gris-moyen transition-shadow'}
|
||||
}) }}
|
||||
{# Implémentation d'un message d'errer en cas de problème #}
|
||||
<div class="text-red-500 text-xs mt-1 italic font-sora">
|
||||
{{ form_errors(form.lastName) }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Champ Téléphone #}
|
||||
<div class="space-y-1">
|
||||
{{ form_label(form.telephone, 'Numéro de téléphone', {
|
||||
'label_attr': {'class': 'block text-sm font-semibold text-text'}
|
||||
}) }}
|
||||
{{ form_widget(form.telephone) }}
|
||||
{# Implémentation d'un message d'errer en cas de problème #}
|
||||
<div class="text-red-500 text-xs mt-1 italic">
|
||||
{{ form_errors(form.telephone) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Champ E-mail #}
|
||||
<div class="space-y-1">
|
||||
{{ form_label(form.email, 'E-mail :', {
|
||||
'label_attr': {'class': 'block text-sm font-semibold text-text'}
|
||||
}) }}
|
||||
{{ form_widget(form.email, {
|
||||
'attr': {'class': 'w-full px-4 py-3 border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton placeholder-gris-moyen transition-shadow'}
|
||||
}) }}
|
||||
{# Implémentation d'un message d'errer en cas de problème #}
|
||||
<div class="text-red-500 text-xs mt-1 italic font-sora">
|
||||
{{ form_errors(form.email) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{# Champ E-mail de secours #}
|
||||
<div class="space-y-1">
|
||||
{{ form_label(form.alternateEmail, 'E-mail de secours :', {
|
||||
'label_attr': {'class': 'block text-sm font-semibold text-text'}
|
||||
}) }}
|
||||
{{ form_widget(form.alternateEmail, {
|
||||
'attr': {'class': 'w-full px-4 py-3 border border-gris-clair rounded-lg focus:outline-none focus:border-bouton focus:ring-1 focus:ring-bouton placeholder-gris-moyen transition-shadow'}
|
||||
}) }}
|
||||
{# Implémentation d'un message d'errer en cas de problème #}
|
||||
<div class="text-red-500 text-xs mt-1 italic font-sora">
|
||||
{{ form_errors(form.alternateEmail) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col sm:flex-row gap-4 pt-2">
|
||||
<button type="submit"
|
||||
class="flex-1 py-3 bg-bouton hover:bg-bouton-hover text-text font-bold rounded-lg shadow transition-colors">
|
||||
Valider
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{{ form_end(form) }}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
@@ -1,11 +0,0 @@
|
||||
{% extends 'base.html.twig' %}
|
||||
|
||||
{% block title %}Accueil | {{ parent() }}{% endblock %}
|
||||
|
||||
{% block body %}
|
||||
<div class="min-h-screen bg-bg-primaire py-8 w-full font-sora">
|
||||
<div>
|
||||
<span>Identifiant : {{ user.identifiantKaz }}</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
Reference in New Issue
Block a user
On va pouvoir regarder comment fonctionne l'api :) et réactiver cette route
tout à fait ! pour l'instant, je l'ai ignorée volontairement et je me suis basée sur mes fixtures pour vérifier que ma mise en page était OK