diff --git a/Todo.md b/Todo.md index 86e02a6..ff78cd5 100644 --- a/Todo.md +++ b/Todo.md @@ -21,4 +21,10 @@ ## Nouvelles fonctionnalités - [ ] Ajouter un filtre dans inscriptions pour filtrer sur une UV ("je veux faire du mt23, n'afficher que les créneaux avec du mt23) - [ ] Ajouter un moyen de joindre les tuteur.ice / tutoré.e ? (type Messenger) en cas d'imprévu -- [ ] Permettre aux admin de retirer une UV dans ViewUvsProposees \ No newline at end of file +- [ ] Permettre aux admin de retirer une UV dans ViewUvsProposees + + + +Une fois l'adresse mail recup +- Mail de confirmation d'email pour créer le compte +- Vérifier le reset du mdp par mail \ No newline at end of file diff --git a/app/Filament/Pages/Register.php b/app/Filament/Pages/Register.php new file mode 100644 index 0000000..dcf5a3f --- /dev/null +++ b/app/Filament/Pages/Register.php @@ -0,0 +1,247 @@ + | null + */ + public ?array $data = []; + + protected string $userModel; + + public function mount(): void + { + if (Filament::auth()->check()) { + redirect()->intended(Filament::getUrl()); + } + + $this->form->fill(); + } + + public function register(): void + { + try { + $this->rateLimit(5); // 5 tentatives avant de se prendre un TO de 1 minute + } catch (TooManyRequestsException $exception) { + Notification::make() + ->title(__('filament-panels::pages/auth/register.notifications.throttled.title', [ + 'seconds' => $exception->secondsUntilAvailable, + 'minutes' => ceil($exception->secondsUntilAvailable / 60), + ])) + ->body(array_key_exists('body', __('filament-panels::pages/auth/register.notifications.throttled') ?: []) ? __('filament-panels::pages/auth/register.notifications.throttled.body', [ + 'seconds' => $exception->secondsUntilAvailable, + 'minutes' => ceil($exception->secondsUntilAvailable / 60), + ]) : null) + ->danger() + ->send(); + + return; + } + + $data = $this->form->getState(); + + $userData = collect($data)->only([ + 'firstName', + 'lastName', + 'email', + 'password', + ])->toArray(); + + $userData['exp'] = time() + 3600; + + app()->bind( + \Illuminate\Auth\Listeners\SendEmailVerificationNotification::class, + \Filament\Listeners\Auth\SendEmailVerificationNotification::class, + ); + //event(new Registered($user)); + + $privateKey = config("services.crypt.private"); + + $token = JWT::encode($userData, $privateKey, 'RS256'); + $email = $userData['email']; + $link = config('app.url').'/registration?token='.urlencode($token); + + \Illuminate\Support\Facades\Notification::route('mail', $email) + ->notify(new CreateAccountNotification($token, $link, $userData['firstName'].' '.$userData['lastName'])); + + Notification::make() + ->title("Un email de confirmation vous a été envoyé.") + ->body("Votre compte sera créé une fois que vous aurez validé votre email.") + ->success() + ->send(); + + $this->redirect(route('filament.tutut.auth.login'), navigate: true); + + return; + } + + public function form(Form $form): Form + { + return $form; + } + + /** + * @return array + */ + protected function getForms(): array + { + return [ + 'form' => $this->form( + $this->makeForm() + ->schema([ + $this->getFirstNameFormComponent(), + $this->getLastNameFormComponent(), + $this->getEmailFormComponent(), + $this->getPasswordFormComponent(), + $this->getPasswordConfirmationFormComponent(), + ]) + ->statePath('data'), + ), + ]; + } + + protected function getFirstNameFormComponent(): Component + { + return TextInput::make('firstName') + ->label('Prénom') + ->required() + ->maxLength(255) + ->autofocus(); + } + + protected function getLastNameFormComponent(): Component + { + return TextInput::make('lastName') + ->label('Nom') + ->required() + ->maxLength(255); + } + + protected function getEmailFormComponent(): Component + { + return TextInput::make('email') + ->label('Email') + ->email() + ->required() + ->maxLength(255) + ->unique($this->getUserModel()) + ->rule('regex:/^[^@]+@.*utc.*$/i') + ->validationMessages([ + 'regex' => 'Seules les adresses email UTC (@etu.utc.fr, @hds.utc.fr...) sont acceptées.', + ]) + ->validationAttribute('Cette adresse email est déjà utilisée.'); + } + + protected function getPasswordFormComponent(): Component + { + return TextInput::make('password') + ->label('Mot de passe') + ->password() + ->required() + ->rule(Password::default()) + ->dehydrateStateUsing(fn ($state) => Hash::make($state)) + ->same('passwordConfirmation') + ->validationMessages([ + 'min' => 'Le mot de passe doit contenir au moins :min caractères.', + 'same' => 'Les deux mots de passe doivent être identiques.', + ]); + } + + protected function getPasswordConfirmationFormComponent(): Component + { + return TextInput::make('passwordConfirmation') + ->label(__('filament-panels::pages/auth/register.form.password_confirmation.label')) + ->password() + ->required() + ->dehydrated(false) + ->validationMessages([ + 'min' => 'Le mot de passe doit contenir au moins :min caractères.', + 'same' => 'Les deux mots de passe doivent être identiques.', + ]); + } + + public function loginAction(): Action + { + return Action::make('login') + ->link() + ->label(__('filament-panels::pages/auth/register.actions.login.label')) + ->url(filament()->getLoginUrl()); + } + + protected function getUserModel(): string + { + if (isset($this->userModel)) { + return $this->userModel; + } + + /** @var SessionGuard $authGuard */ + $authGuard = Filament::auth(); + + /** @var EloquentUserProvider $provider */ + $provider = $authGuard->getProvider(); + + return $this->userModel = $provider->getModel(); + } + + public function getHeading(): string | Htmlable + { + return "Créer un compte"; + } + + /** + * @return array + */ + protected function getFormActions(): array + { + return [ + $this->getRegisterFormAction(), + ]; + } + + public function getRegisterFormAction(): Action + { + return Action::make('register') + ->label(__('filament-panels::pages/auth/register.form.actions.register.label')) + ->submit('register'); + } + + protected function hasFullWidthFormActions(): bool + { + return true; + } +} \ No newline at end of file diff --git a/app/Filament/Pages/RequestPasswordReset.php b/app/Filament/Pages/RequestPasswordReset.php new file mode 100644 index 0000000..bb45648 --- /dev/null +++ b/app/Filament/Pages/RequestPasswordReset.php @@ -0,0 +1,69 @@ +rateLimit(2); + } catch (\DanHarrin\LivewireRateLimiting\Exceptions\TooManyRequestsException $ex) { + Notification::make() + ->title(__('filament-panels::pages/auth/password-reset/request-password-reset.notifications.throttled.title', [ + 'seconds' => $ex->secondsUntilAvailable, + 'minutes' => ceil($ex->secondsUntilAvailable / 60), + ])) + ->body(__('filament-panels::pages/auth/password-reset/request-password-reset.notifications.throttled.body', [ + 'seconds' => $ex->secondsUntilAvailable, + 'minutes' => ceil($ex->secondsUntilAvailable / 60), + ])) + ->danger() + ->send(); + + return; + } + + $data = $this->form->getState(); + + $status = Password::broker(Filament::getAuthPasswordBroker())->sendResetLink( + $data, + function (CanResetPassword $user, string $token): void { + if (! method_exists($user, 'notify')) { + throw new Exception("Model [" . get_class($user) . "] does not have notify()"); + } + + $token = app('auth.password.broker')->createToken($user); + $notification = new ResetPasswordNotification($token); + $notification->url = \Filament\Facades\Filament::getResetPasswordUrl($token, $user); + $user->notify($notification); + + $this->redirect(route('filament.tutut.auth.login'), navigate: true); + } + ); + + if ($status !== Password::RESET_LINK_SENT) { + Notification::make() + ->title(__($status)) + ->danger() + ->send(); + + return; + } + + Notification::make() + ->title(__($status)) + ->success() + ->send(); + + $this->form->fill(); + } +} diff --git a/app/Http/Controllers/AuthController.php b/app/Http/Controllers/AuthController.php index c0285c1..f4b4843 100644 --- a/app/Http/Controllers/AuthController.php +++ b/app/Http/Controllers/AuthController.php @@ -2,93 +2,146 @@ namespace App\Http\Controllers; -use Illuminate\Http\Request; -use Illuminate\Support\Facades\Auth; -use League\OAuth2\Client\Provider\GenericProvider; use App\Models\User; -use App\Providers\RouteServiceProvider; +use Firebase\JWT\JWT; +use Illuminate\Http\Request; +use Illuminate\Support\Facades\Hash; +use Firebase\JWT\Key; +use Firebase\JWT\SignatureInvalidException; +use LogicException; +use UnexpectedValueException; +use Filament\Facades\Filament; +use Firebase\JWT\ExpiredException; class AuthController extends Controller { - public GenericProvider $provider; - - public function __construct() - { - $this->provider = new GenericProvider([ - 'clientId' => config("auth.oauth.client_id"), - 'clientSecret' => config("auth.oauth.client_secret"), - 'redirectUri' => config("auth.oauth.redirect_uri"), - 'urlAuthorize' => config("auth.oauth.authorize_url"), - 'urlAccessToken' => config("auth.oauth.access_token_url"), - 'urlResourceOwnerDetails' => config("auth.oauth.owner_details_url"), - 'scopes' => config("auth.oauth.scopes"), - ]); - } - - public function login(Request $request) + public function register(Request $request) { - if (config('auth.app_no_login', false)) { - try { - $userID = config('auth.auto_user_id'); - $user = User::find($userID); - if ($user) { - Auth::login($user); - return redirect(RouteServiceProvider::HOME); - } - } catch (\Exception $e) { - return response()->json(['message' => 'Login error :'. $e->getMessage()], 400); - } + $publicKey = config('services.crypt.public'); + $token = $request->query('token'); + + if (!$token) { + return response()->json(['message'=>"Token absent"],400); } - - $state = bin2hex(random_bytes(16)); - $request->session()->put('oauth2state', $state); - - $authorizationUrl = $this->provider->getAuthorizationUrl([ - 'state' => $state - ]); - - return redirect($authorizationUrl); - } - - public function callback(Request $request) - { - $storedState = $request->session()->pull('oauth2state'); - - if (!$request->has('state') || $request->get('state') !== $storedState) { - abort(400, 'Invalid state: '. $request->get('state') . ' VS ' . $storedState); + try{ + $decoded = JWT::decode($token, new Key($publicKey, 'RS256')); + } catch(ExpiredException){ + return response()->json(['message'=>'Signature expirée'],401); + }catch(SignatureInvalidException){ + return response()->json(['message'=>'Signature invalide'],401); + } catch (LogicException $e) { + return response()->json(['message' => 'Erreur dans la configuration ou les clés '], 400); + } catch (UnexpectedValueException $e) { + return response()->json(['message' => 'Le token est mal formé ou contient des données invalides'], 400); + } + + $existingUser = User::where('email', $decoded->email)->first(); + if($existingUser) { + return response()->json(['message'=>'Cet email est déjà utilisé par un compte existant'],400); } + + $user = User::create([ + 'firstName' => $decoded->firstName, + 'lastName' => $decoded->lastName, + 'email' => $decoded->email, + 'password' => Hash::make($decoded->password), + ]); - if (!$request->has('code')) { - abort(400, 'No authorization code'); - } - try { - $accessToken = $this->provider->getAccessToken('authorization_code', [ - 'code' => $request->get('code'), - ]); - - $resourceOwner = $this->provider->getResourceOwner($accessToken); - $userDetails = $resourceOwner->toArray(); - - if ($userDetails['deleted_at'] != null || $userDetails['active'] != 1) { - abort(401, 'Compte supprimé ou désactivé'); - } - - $user = User::where('email', $userDetails['email'])->first(); - if(!$user->firstName){ - $user->firstName = $userDetails["firstName"]; - $user->lastName = $userDetails["lastName"]; - $user->save(); - } - - Auth::login($user); - return redirect(RouteServiceProvider::HOME); - } catch (\Exception $e) { - abort(400, 'Callback error : ' . $e->getMessage()); - } - } + $request->session()->invalidate(); + $request->session()->regenerateToken(); + Filament::auth()->login($user); - public function logout() { - Auth::logout(); - return redirect(config('auth.oauth.logout_url')); + return redirect()->route('filament.tutut.pages.dashboard'); } -} \ No newline at end of file +} + +// use Illuminate\Http\Request; +// use Illuminate\Support\Facades\Auth; +// use League\OAuth2\Client\Provider\GenericProvider; +// use App\Models\User; +// use App\Providers\RouteServiceProvider; + +// class AuthController extends Controller +// { +// public GenericProvider $provider; + +// public function __construct() +// { +// $this->provider = new GenericProvider([ +// 'clientId' => config("auth.oauth.client_id"), +// 'clientSecret' => config("auth.oauth.client_secret"), +// 'redirectUri' => config("auth.oauth.redirect_uri"), +// 'urlAuthorize' => config("auth.oauth.authorize_url"), +// 'urlAccessToken' => config("auth.oauth.access_token_url"), +// 'urlResourceOwnerDetails' => config("auth.oauth.owner_details_url"), +// 'scopes' => config("auth.oauth.scopes"), +// ]); +// } + +// public function login(Request $request) +// { +// if (config('auth.app_no_login', false)) { +// try { +// $userID = config('auth.auto_user_id'); +// $user = User::find($userID); +// if ($user) { +// Auth::login($user); +// return redirect(RouteServiceProvider::HOME); +// } +// } catch (\Exception $e) { +// return response()->json(['message' => 'Login error :'. $e->getMessage()], 400); +// } +// } + +// $state = bin2hex(random_bytes(16)); +// $request->session()->put('oauth2state', $state); + +// $authorizationUrl = $this->provider->getAuthorizationUrl([ +// 'state' => $state +// ]); + +// return redirect($authorizationUrl); +// } + +// public function callback(Request $request) +// { +// $storedState = $request->session()->pull('oauth2state'); + +// if (!$request->has('state') || $request->get('state') !== $storedState) { +// abort(400, 'Invalid state: '. $request->get('state') . ' VS ' . $storedState); +// } + +// if (!$request->has('code')) { +// abort(400, 'No authorization code'); +// } +// try { +// $accessToken = $this->provider->getAccessToken('authorization_code', [ +// 'code' => $request->get('code'), +// ]); + +// $resourceOwner = $this->provider->getResourceOwner($accessToken); +// $userDetails = $resourceOwner->toArray(); + +// if ($userDetails['deleted_at'] != null || $userDetails['active'] != 1) { +// abort(401, 'Compte supprimé ou désactivé'); +// } + +// $user = User::where('email', $userDetails['email'])->first(); +// if(!$user->firstName){ +// $user->firstName = $userDetails["firstName"]; +// $user->lastName = $userDetails["lastName"]; +// $user->save(); +// } + +// Auth::login($user); +// return redirect(RouteServiceProvider::HOME); +// } catch (\Exception $e) { +// abort(400, 'Callback error : ' . $e->getMessage()); +// } +// } + +// public function logout() { +// Auth::logout(); +// return redirect()->route('filament.auth.login'); +// } +// } \ No newline at end of file diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index 83ed965..0a6a089 100644 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -17,9 +17,9 @@ class Authenticate * @return mixed */ public function handle(Request $request, Closure $next): Response - { - if (!Auth::check()) { - return redirect()->route('login'); + { + if (!Auth::guard('web')->check()) { + return redirect()->route('filament.tutut.auth.login'); } return $next($request); diff --git a/app/Models/User.php b/app/Models/User.php index 3cc2c3e..c36a68f 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -5,19 +5,34 @@ use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Filament\Models\Contracts\HasName; -use Filament\Panel; use App\Enums\Roles; +use Illuminate\Notifications\Notifiable; +use App\Notifications\ResetPasswordNotification; +use Filament\Models\Contracts\FilamentUser; +use Filament\Panel; -class User extends Authenticatable implements HasName +class User extends Authenticatable implements HasName, FilamentUser { - use HasFactory; + use HasFactory, Notifiable; + + protected $fillable = ['email', 'firstName', 'lastName', 'password', 'role', 'languages', 'rgpd_accepted_at']; - protected $fillable = ['email', 'firstName', 'lastName', 'role', 'languages', 'rgpd_accepted_at']; + protected $hidden = ['password', 'remember_token']; protected $casts = [ 'languages' => 'array', ]; + public function sendPasswordResetNotification($token): void + { + $this->notify(new ResetPasswordNotification($token)); + } + + public function canAccessPanel(Panel $panel): bool + { + return true; + } + public function getFilamentName(): string { return ($this->firstName." ".$this->lastName); diff --git a/app/Notifications/CreateAccountNotification.php b/app/Notifications/CreateAccountNotification.php new file mode 100644 index 0000000..fbff38e --- /dev/null +++ b/app/Notifications/CreateAccountNotification.php @@ -0,0 +1,41 @@ +token = $token; + if ($url) { + $this->url = $url; + } + $this->name = $name; + } + + public function via(): array + { + return ['mail']; + } + + public function toMail(): MailMessage + { + return (new MailMessage) + ->subject("Création de votre compte Tut'ut !") + ->greeting('Hellooo '.$this->name) + ->line("Tu as essayé de créer un compte sur le site Tut'ut ?") + ->line("Afin de valider ton compte, tu dois cliquer sur le lien ci-dessous :") + ->action('Créer mon compte !', $this->url) + ->line("Si tu n'es pas à l'origine de cette demande, tu peux ignorer ce mail.") + ->salutation("Pédagogiquement,\nL'équipe de Tut'ut !"); + } +} + + diff --git a/app/Notifications/ResetPasswordNotification.php b/app/Notifications/ResetPasswordNotification.php new file mode 100644 index 0000000..82e2b65 --- /dev/null +++ b/app/Notifications/ResetPasswordNotification.php @@ -0,0 +1,39 @@ +token = $token; + if ($url) { + $this->url = $url; + } + } + + public function via(object $notifiable): array + { + return ['mail']; + } + + public function toMail(object $notifiable): MailMessage + { + return (new MailMessage) + ->subject("Réinitialisation de ton mot de passe Tut'ut") + ->greeting('Hellooo') + ->line('Tu as demandé la réinitialisation de votre mot de passe ?') + ->line('Pour réinitialiser ton mot de passe, tu dois cliquer sur le lien ci-dessous :') + ->action('Réinitialiser mon mot de passe', $this->url) + ->line("Si tu n'as pas demandé cette réinitialisation, tu peux ignorer ce mail.") + ->salutation("Pédagogiquement,\nL'équipe de Tut'ut !"); + } +} + + diff --git a/app/Providers/Filament/AdminPanelProvider.php b/app/Providers/Filament/AdminPanelProvider.php index bb8bee6..1e95027 100644 --- a/app/Providers/Filament/AdminPanelProvider.php +++ b/app/Providers/Filament/AdminPanelProvider.php @@ -2,6 +2,8 @@ namespace App\Providers\Filament; +use App\Filament\Pages\RequestPasswordReset; +use App\Filament\Pages\Register; use App\Filament\Pages\CalendarManager; use App\Filament\Pages\SendEmail; use App\Filament\Pages\SettingsPage; @@ -67,6 +69,10 @@ public function panel(Panel $panel): Panel ->default() ->id('tutut') ->path('/') + ->login() + ->registration(Register::class) + ->passwordReset(RequestPasswordReset::class) + ->authGuard('web') ->brandName("Tut'ut - ".$semestreActif) ->colors([ 'primary' => Color::Blue, @@ -110,7 +116,7 @@ public function panel(Panel $panel): Panel EnsureRgpdAccepted::class, ]) ->authMiddleware([ - Authenticate::class, + Authenticate::class ]); } } diff --git a/composer.json b/composer.json index 2fca6b8..7465bdb 100644 --- a/composer.json +++ b/composer.json @@ -9,6 +9,7 @@ "php": "^8.2", "bezhansalleh/filament-language-switch": "^3.1", "filament/filament": "^3.2", + "firebase/php-jwt": "^6.11", "laravel/framework": "^11.31", "laravel/sanctum": "^4.0", "laravel/tinker": "^2.9", diff --git a/composer.lock b/composer.lock index 329326e..3d9993f 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ea39aaf493d5ee5a257e06df64fef174", + "content-hash": "cd5ba6924e1ded5dfef14326cafeb751", "packages": [ { "name": "anourvalar/eloquent-serialize", @@ -1568,6 +1568,69 @@ }, "time": "2025-03-11T16:33:32+00:00" }, + { + "name": "firebase/php-jwt", + "version": "v6.11.1", + "source": { + "type": "git", + "url": "https://github.com/firebase/php-jwt.git", + "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/firebase/php-jwt/zipball/d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", + "reference": "d1e91ecf8c598d073d0995afa8cd5c75c6e19e66", + "shasum": "" + }, + "require": { + "php": "^8.0" + }, + "require-dev": { + "guzzlehttp/guzzle": "^7.4", + "phpspec/prophecy-phpunit": "^2.0", + "phpunit/phpunit": "^9.5", + "psr/cache": "^2.0||^3.0", + "psr/http-client": "^1.0", + "psr/http-factory": "^1.0" + }, + "suggest": { + "ext-sodium": "Support EdDSA (Ed25519) signatures", + "paragonie/sodium_compat": "Support EdDSA (Ed25519) signatures when libsodium is not present" + }, + "type": "library", + "autoload": { + "psr-4": { + "Firebase\\JWT\\": "src" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "BSD-3-Clause" + ], + "authors": [ + { + "name": "Neuman Vong", + "email": "neuman+pear@twilio.com", + "role": "Developer" + }, + { + "name": "Anant Narayanan", + "email": "anant@php.net", + "role": "Developer" + } + ], + "description": "A simple library to encode and decode JSON Web Tokens (JWT) in PHP. Should conform to the current spec.", + "homepage": "https://github.com/firebase/php-jwt", + "keywords": [ + "jwt", + "php" + ], + "support": { + "issues": "https://github.com/firebase/php-jwt/issues", + "source": "https://github.com/firebase/php-jwt/tree/v6.11.1" + }, + "time": "2025-04-09T20:32:01+00:00" + }, { "name": "fruitcake/php-cors", "version": "v1.3.0", diff --git a/config/app.php b/config/app.php index e17cea6..fe5b741 100644 --- a/config/app.php +++ b/config/app.php @@ -54,7 +54,7 @@ | */ - 'url' => env('APP_URL', 'http://localhost'), + 'url' => env('APP_URL', 'http://localhost/tutut'), /* |-------------------------------------------------------------------------- diff --git a/config/filament.php b/config/filament.php new file mode 100644 index 0000000..65b4bf0 --- /dev/null +++ b/config/filament.php @@ -0,0 +1,89 @@ + [ + + // 'echo' => [ + // 'broadcaster' => 'pusher', + // 'key' => env('VITE_PUSHER_APP_KEY'), + // 'cluster' => env('VITE_PUSHER_APP_CLUSTER'), + // 'wsHost' => env('VITE_PUSHER_HOST'), + // 'wsPort' => env('VITE_PUSHER_PORT'), + // 'wssPort' => env('VITE_PUSHER_PORT'), + // 'authEndpoint' => '/broadcasting/auth', + // 'disableStats' => true, + // 'encrypted' => true, + // 'forceTLS' => true, + // ], + + ], + + /* + |-------------------------------------------------------------------------- + | Default Filesystem Disk + |-------------------------------------------------------------------------- + | + | This is the storage disk Filament will use to store files. You may use + | any of the disks defined in the `config/filesystems.php`. + | + */ + + 'default_filesystem_disk' => env('FILAMENT_FILESYSTEM_DISK', 'public'), + + /* + |-------------------------------------------------------------------------- + | Assets Path + |-------------------------------------------------------------------------- + | + | This is the directory where Filament's assets will be published to. It + | is relative to the `public` directory of your Laravel application. + | + | After changing the path, you should run `php artisan filament:assets`. + | + */ + + 'assets_path' => null, + + /* + |-------------------------------------------------------------------------- + | Cache Path + |-------------------------------------------------------------------------- + | + | This is the directory that Filament will use to store cache files that + | are used to optimize the registration of components. + | + | After changing the path, you should run `php artisan filament:cache-components`. + | + */ + + 'cache_path' => base_path('bootstrap/cache/filament'), + + /* + |-------------------------------------------------------------------------- + | Livewire Loading Delay + |-------------------------------------------------------------------------- + | + | This sets the delay before loading indicators appear. + | + | Setting this to 'none' makes indicators appear immediately, which can be + | desirable for high-latency connections. Setting it to 'default' applies + | Livewire's standard 200ms delay. + | + */ + + 'livewire_loading_delay' => 'default', + +]; diff --git a/config/services.php b/config/services.php index 27a3617..efa9264 100644 --- a/config/services.php +++ b/config/services.php @@ -35,4 +35,8 @@ ], ], + 'crypt' => [ + 'public' => file_get_contents(storage_path(env('JWT_PUBLIC_KEY_PATH', 'app/keys/public.key'))), + 'private' => file_get_contents(storage_path(env('JWT_PRIVATE_KEY_PATH', 'app/keys/private.key'))), + ], ]; diff --git a/database/migrations/0000_create_users_table.php b/database/migrations/0000_create_users_table.php index 3efebc0..fc37c44 100644 --- a/database/migrations/0000_create_users_table.php +++ b/database/migrations/0000_create_users_table.php @@ -19,6 +19,8 @@ public function up(): void $table->string('email', 200); $table->string('firstName', 100)->nullable(); $table->string('lastName', 100)->nullable(); + $table->string('password'); + $table->rememberToken(); $table->enum('role', $roles)->default('tutee'); $table->json('languages')->nullable(); $table->timestamp('rgpd_accepted_at')->nullable(); diff --git a/database/migrations/2025_08_26_192215_create_password_reset_tokens_table.php b/database/migrations/2025_08_26_192215_create_password_reset_tokens_table.php new file mode 100644 index 0000000..fda2b31 --- /dev/null +++ b/database/migrations/2025_08_26_192215_create_password_reset_tokens_table.php @@ -0,0 +1,21 @@ +string('email')->index(); + $table->string('token'); + $table->timestamp('created_at')->nullable(); + }); + } + + public function down(): void + { + Schema::dropIfExists('password_reset_tokens'); + } +}; diff --git a/nginx.conf b/nginx.conf deleted file mode 100644 index 79bfd49..0000000 --- a/nginx.conf +++ /dev/null @@ -1,20 +0,0 @@ -server { - listen 80; - index index.php index.html; - root /var/www/html/public; - - location / { - try_files $uri $uri/ /index.php?$query_string; - } - - location ~ \.php$ { - include fastcgi_params; - fastcgi_pass php:8000; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; - fastcgi_index index.php; - } - - location ~ /\.ht { - deny all; - } -} diff --git a/phpunit.xml b/phpunit.xml deleted file mode 100644 index 506b9a3..0000000 --- a/phpunit.xml +++ /dev/null @@ -1,33 +0,0 @@ - - - - - tests/Unit - - - tests/Feature - - - - - app - - - - - - - - - - - - - - - - diff --git a/resources/lang/en/pages.php b/resources/lang/en/pages.php new file mode 100644 index 0000000..577f9c1 --- /dev/null +++ b/resources/lang/en/pages.php @@ -0,0 +1,76 @@ + [ + 'tutor_rejected' => [ + 'title' => 'Your application has been rejected.', + 'message' => 'You can modify your request and submit it again for a new evaluation.', + ], + ], + 'tables' => [ + 'columns' => [ + 'semaines_heures' => [ + 'semaine' => 'Week', + 'no_results' => 'All hours for this month have already been entered.', + ], + 'total_heures' => [ + 'total' => 'Total', + ], + ], + ], + 'become_tutor' => [ + // This page doesn't have any text that needs translation + ], + 'calendar_manager' => [ + 'previous_month' => 'Previous month', + 'next_month' => 'Next month', + 'holiday' => 'Holiday', + 'navigation_label' => 'Semester Calendar', + 'title' => 'Semester Calendar', + 'selected_date' => 'Date to modify', + 'schedule_modification' => 'Schedule Modification', + 'schedule_description' => 'Define schedule changes or holidays', + 'holiday_help_text' => 'Check this box for a holiday', + 'day_template' => 'Day Template', + 'days' => [ + 'monday' => 'Monday', + 'tuesday' => 'Tuesday', + 'wednesday' => 'Wednesday', + 'thursday' => 'Thursday', + 'friday' => 'Friday', + 'saturday' => 'Saturday', + 'sunday' => 'Sunday', + ], + 'select_day' => 'Select a day', + 'save' => 'Save', + 'delete' => 'Delete', + 'date_out_of_range' => 'Date outside active semester', + 'date_must_be_between' => 'The selected date must be between :start and :end', + 'modification_saved' => 'Change saved', + 'modification_deleted' => 'Change deleted', + ], + 'send_email' => [ + 'preview' => 'Email preview', + 'send' => 'Send email', + 'save_template' => 'Save as template', + 'delete_template' => 'Delete template', + 'close' => 'Close', + ], + 'settings_page' => [ + // This page doesn't have any text that needs translation + ], + 'tutor_manage_uvs' => [ + 'add' => 'Add', + 'update_languages' => 'Update my languages', + ], + 'rgpd' => [ + 'title' => 'GDPR Consent', + 'data_collection' => 'To enable the proper functioning of the application, we collect some of your personal data:', + 'optional_data' => 'Optionally for tutors:', + 'data_storage' => 'This data is securely stored on UTC servers and may potentially be exported by the Student Life Office to improve Tut\'ut', + 'accept' => 'I accept', + 'language' => 'Language', + 'french' => 'French', + 'english' => 'English', + ], +]; \ No newline at end of file diff --git a/resources/lang/en/resources.php b/resources/lang/en/resources.php new file mode 100644 index 0000000..c46468f --- /dev/null +++ b/resources/lang/en/resources.php @@ -0,0 +1,534 @@ + [ + 'label' => 'Account Management', + 'plural_label' => 'Account Management', + 'create_label' => 'Create an account', + 'fields' => [ + 'email' => 'Email Address', + 'role' => 'Role', + ], + 'roles' => [ + 'administrator' => 'Administrator', + 'employed_tutor' => 'Employed Tutor', + 'employed_privileged_tutor' => 'Privileged Tutor', + 'tutor' => 'Tutor', + 'tutee' => 'Tutee', + ], + 'actions' => [ + 'delete' => 'Revoke Role', + 'upgrade' => 'Upgrade', + 'downgrade' => 'Downgrade', + ], + ], + 'common' => [ + 'navigation' => [ + 'tutorat' => 'Tutoring', + ], + 'fields' => [ + 'salle' => 'Room', + 'semaine' => 'Week', + 'tuteur1' => 'Tutor 1', + 'tuteur2' => 'Tutor 2', + 'uvs_proposees' => 'Offered Courses', + 'uvs_souhaitees' => 'Desired Courses', + 'jour_et_horaire' => 'Day and Time', + 'email' => 'Email', + 'password' => 'Password', + 'remember_me' => 'Remember me', + 'start' => 'Schedule', + 'date_debut' => 'Start Date', + 'date_fin' => 'End Date', + 'places' => 'Seats', + ], + 'buttons' => [ + 's_inscrire' => 'Register', + 'se_desinscrire' => 'Unregister', + 'shotgun_1' => 'Register 1', + 'shotgun_2' => 'Register 2', + 'login' => 'Login', + 'logout' => 'Logout', + 'register' => 'Register', + 'save' => 'Save', + 'cancel' => 'Cancel', + 'close' => 'Close', + 'create' => 'Create', + 'edit' => 'Edit', + 'delete' => 'Delete', + 'view_registrations' => 'View Registrations', + 'export_excel' => 'Export to Excel', + ], + 'placeholders' => [ + 'none' => '—', + ], + 'format' => [ + 'semaine_numero' => 'Week :number', + ], + ], + 'creneau' => [ + 'label' => 'Time Slot', + 'plural_label' => 'Time Slots', + 'navigation_label' => 'Shotgun Time Slots', + ], + 'inscription_creneau' => [ + 'label' => 'Time Slot Registration', + 'plural_label' => 'Time Slot Registrations', + 'navigation_label' => 'Tutoring Registrations', + 'semaine' => 'Week', + 'semaine_actuelle' => 'Current week', + 'semaine_prochaine' => 'Next Week', + 'modal_heading' => 'List of registrants', + ], + 'login' => [ + 'title' => 'Login', + 'subtitle' => 'Sign in to your account', + 'forgot_password' => 'Forgot your password?', + ], + 'become_tutor' => [ + 'navigation_label' => 'Become a Tutor', + 'section_title' => 'Tutor Application', + 'section_description' => 'Fill this form to become a tutor', + 'title' => 'Become a Tutor', + 'fields' => [ + 'firstName' => 'First Name', + 'lastName' => 'Last Name', + 'email' => 'Email', + 'semester' => 'Semester (e.g. TC03)', + 'semester_helper' => 'Format: XXNN (e.g. TC03, GI02)', + 'motivation' => 'Motivation', + 'motivation_placeholder' => 'Describe why you want to become a tutor and your relevant experiences', + 'uvs' => 'Validated Courses', + 'uvs_helper' => 'Select the subjects you can teach', + ], + 'actions' => [ + 'save' => 'Save', + 'delete' => 'Delete Application', + 'delete_modal_title' => 'Delete Application', + 'delete_modal_description' => 'Are you sure you want to delete your application?', + ], + 'notifications' => [ + 'deleted_title' => 'Application Deleted', + 'deleted_body' => 'Your application has been successfully deleted.', + 'submitted_title' => 'Application Submitted Successfully', + 'submitted_body' => 'Your request to become a tutor has been recorded and is pending approval.', + ], + ], + 'comptabilite' => [ + 'label' => 'Accounting', + 'plural_label' => 'Accounting', + 'fields' => [ + 'comment_bve' => 'BVE Comment', + 'month' => 'Month', + 'tutor' => 'Tutor', + 'validated' => 'Validated', + 'semaine_heures' => 'Weeks and Hours', + 'total' => 'Total', + 'heures_supp' => 'Extra Hours', + ], + 'actions' => [ + 'modify' => 'Modify', + ], + 'notifications' => [ + 'validated_title' => 'Validation Complete', + 'validated_body' => 'Hours have been successfully validated.', + ], + 'placeholders' => [ + 'no_active_semester' => 'No active semester', + ], + 'filters' => [ + 'not_validated' => 'Not Validated', + ], + ], + 'comptabilite_tutor' => [ + 'label' => 'Accounting', + 'plural_label' => 'Accounting', + 'fields' => [ + 'only_unaccounted' => 'Show only uncounted slots', + 'heures_supp' => 'Extra Hours', + 'duree' => 'Duration (hours)', + 'semaine' => 'Week', + 'heures_comptabilisees' => 'Accounted Hours', + ], + 'actions' => [ + 'confirm_hours' => 'Confirm Hours', + 'cancel' => 'Cancel', + 'save' => 'Save', + ], + 'notifications' => [ + 'hours_updated' => 'Hours Updated', + ], + 'subheadings' => [ + 'save_reminder' => 'Remember to save before leaving this page', + ], + ], + 'components' => [ + 'slot_creneau' => [ + 'inscrits' => ':count registered', + 'present' => 'Present', + 'absent' => 'Absent', + ], + 'no_creneaux' => [ + 'title' => 'No time slots found', + 'description' => 'You have no time slots to validate for this week.', + ], + ], + 'admin' => [ + 'navigation_group' => [ + 'administration' => 'Administration', + 'gestion' => 'Management', + ], + 'salle' => [ + 'label' => 'Room', + 'plural_label' => 'Rooms', + 'fields' => [ + 'numero' => 'Room number', + 'disponibilites' => 'Availability', + 'heure_debut' => 'Start time', + 'heure_fin' => 'End time', + ], + 'jours' => [ + 'lundi' => 'Monday', + 'mardi' => 'Tuesday', + 'mercredi' => 'Wednesday', + 'jeudi' => 'Thursday', + 'vendredi' => 'Friday', + 'samedi' => 'Saturday', + 'medians' => 'Midterms', + 'finaux' => 'Finals', + ], + ], + 'semaine' => [ + 'label' => 'Week', + 'plural_label' => 'Weeks', + 'fields' => [ + 'numero' => 'Number', + 'fk_semestre' => 'Semester', + 'date_debut' => 'Start date', + 'date_fin' => 'End date', + 'is_vacances' => 'Holiday', + ], + 'filters' => [ + 'future' => 'Future weeks', + ], + 'actions' => [ + 'creer_prochaine' => 'Create next week', + 'generer_creneaux' => '(Re)Generate time slots', + 'vacances' => 'Holidays', + ], + 'messages' => [ + 'pas_generation_vacances' => 'No time slots generation for holiday weeks.', + 'creneaux_generes' => 'Time slots successfully generated for week :numero.', + ], + ], + 'semestre' => [ + 'label' => 'Semester', + 'plural_label' => 'Semesters', + 'fields' => [ + 'code' => 'Code', + 'is_active' => 'Active', + 'debut' => 'Start date', + 'fin' => 'End date', + 'debut_medians' => 'Midterms start', + 'fin_medians' => 'Midterms end', + 'debut_finaux' => 'Finals start', + 'fin_finaux' => 'Finals end', + 'helperText_is_vacances' => 'No slot will be generated for this week', + ], + 'actions' => [ + 'activer' => 'Activate', + ], + 'values' => [ + 'oui' => 'Yes', + 'non' => 'No', + ], + ], + 'comptabilite' => [ + 'label' => 'Accounting', + 'plural_label' => 'Accounting', + 'fields' => [ + 'commentaire_bve' => 'BVE Comment', + 'no_semestre_actif' => 'No active semester', + 'mois' => 'Month', + 'tuteur' => 'Tutor', + 'valide' => 'Validated', + 'semaines_heures' => 'Weeks and Hours', + 'total' => 'Total', + 'heures_supplementaires' => 'Extra Hours', + 'nombre_heures_supplementaires' => 'Number of extra hours', + 'justification' => 'Justification', + ], + 'filters' => [ + 'non_valides' => 'Not validated', + ], + 'actions' => [ + 'modifier' => 'Edit', + 'valider' => 'Validate', + 'annuler' => 'Cancel', + 'commentaire' => 'Comment', + ], + 'modals' => [ + 'valider_heures' => 'Validate hours', + 'valider_confirmation' => 'Do you want to validate :name\'s hours?', + 'valider_oui' => 'Yes, validate', + 'annuler_heures' => 'Cancel hours validation', + 'annuler_confirmation' => 'Do you want to cancel :name\'s hours validation?', + 'annuler_oui' => 'Yes, cancel', + ], + ], + ], + 'feedback' => [ + 'label' => 'Feedback', + 'plural_label' => 'Feedback', + 'tutee_label' => 'My Feedback', + 'fields' => [ + 'text' => 'Give us your feedback !', + ], + 'actions' => [ + 'delete' => [ + 'modal_heading' => 'Delete feedback', + 'modal_subheading' => 'Are you sure you want to delete this feedback?', + 'modal_button' => 'Delete', + ], + ], + ], + 'tutor_application' => [ + 'label' => 'Tutor Application', + 'plural_label' => 'Tutor Applications', + 'navigation_group' => 'Management', + 'fields' => [ + 'firstname' => 'First Name', + 'lastname' => 'Last Name', + 'semester' => 'Semester', + 'status' => 'Status', + ], + 'status' => [ + 'pending' => 'Pending', + 'rejected' => 'Rejected', + ], + 'filters' => [ + 'semester' => 'Semester', + 'pending' => 'Pending only', + ], + 'actions' => [ + 'view' => [ + 'label' => 'More details', + 'modal_heading' => 'Application Details', + 'modal_cancel_label' => 'Close', + ], + 'accept' => [ + 'label' => 'Accept', + 'modal_heading' => 'Confirm Acceptance', + 'modal_description' => 'Are you sure you want to accept this application?', + 'notification_title' => 'Application accepted', + ], + 'reject' => [ + 'label' => 'Reject', + 'modal_heading' => 'Confirm Rejection', + 'modal_description' => 'Are you sure you want to reject this application?', + 'notification_title' => 'Application rejected', + ], + ], + 'sections' => [ + 'personal_info' => 'Personal Information', + 'uvs' => 'Offered Courses', + 'motivation' => 'Motivation', + ], + ], + 'widgets' => [ + 'admin' => [ + 'stats' => [ + 'avg_tutees_per_night' => 'Average tutees / night', + 'avg_tutees_per_slot' => 'Average tutees / slot', + 'total_active_tutees' => 'Total active tutees this semester', + 'open_slots_per_week' => 'Open slots / week', + 'volunteer_tutors' => 'Volunteer tutors', + 'total_hours' => 'Total hours (accounted for)', + 'most_requested_courses' => 'Most requested courses', + ], + ], + 'tutee_creneaux' => [ + 'heading' => 'My upcoming tutee registrations', + 'columns' => [ + 'day' => 'Day', + 'schedule' => 'Schedule', + 'room' => 'Room', + 'tutor1' => 'Tutor 1', + 'tutor2' => 'Tutor 2', + 'requested_courses' => 'Requested courses', + ], + ], + 'tutor_creneaux' => [ + 'heading' => 'My upcoming tutor slots', + 'columns' => [ + 'day' => 'Day', + 'schedule' => 'Schedule', + 'room' => 'Room', + 'tutor1' => 'Tutor 1', + 'tutor2' => 'Tutor 2', + 'registrations_count' => 'Number of registrations', + 'requested_courses' => 'Requested courses', + ], + ], + ], + 'pages' => [ + 'tutor_manage_uvs' => [ + 'title' => 'My Offered Courses', + 'navigation_group' => 'Tutoring', + 'sections' => [ + 'propose_uv' => [ + 'title' => 'Offer a Course', + 'description' => "If you can't find the course you're looking for, you can ask :tutors to add it", + ], + 'create_new_uv' => [ + 'title' => 'OR create a new course', + 'description' => 'Create a new course with its code and title', + ], + ], + 'fields' => [ + 'languages' => 'Languages spoken', + 'selected_codes' => 'Existing course', + 'code' => "Course code", + 'intitule' => "Course title", + ], + 'notifications' => [ + 'languages_updated_title' => 'Languages updated', + 'languages_updated_body' => 'Your languages have been successfully updated.', + 'uvs_added_title' => 'Course(s) added', + 'uvs_added_body' => 'Your courses have been successfully updated.', + 'uvs_created_title' => 'Course(s) added', + 'uvs_created_body' => "The course has been created and your courses have been successfully updated.", + ], + 'languages' => [ + 'en' => '🇬🇧 English', + 'es' => '🇪🇸 Spanish', + 'zh' => '🇨🇳 Chinese', + 'de' => '🇩🇪 German', + 'ar' => '🇸🇦 Arabic', + 'ru' => '🇷🇺 Russian', + 'ja' => '🇯🇵 Japanese', + 'it' => '🇮🇹 Italian', + ], + ], + 'send_email' => [ + 'title' => 'Send Email', + 'navigation_group' => 'Management', + 'fields' => [ + 'template' => 'Load template', + 'roles' => 'Recipients', + 'mail_title' => 'Subject', + 'content' => 'Content', + 'template_name' => 'Template name', + 'template_name_helper' => "Required only for saving", + ], + 'roles' => [ + 'administrator' => 'Administrator', + 'employed_privileged_tutor' => 'Employed Privileged Tutor', + 'employed_tutor' => 'Employed Tutor', + 'tutor' => 'Tutor', + 'tutee' => 'Tutee', + ], + 'buttons' => [ + 'preview' => 'Preview', + 'send' => 'Send', + 'save_template' => 'Save as template', + 'delete_template' => 'Delete template', + ], + 'notifications' => [ + 'error_title' => 'Error', + 'error_select_role' => 'Please select at least one role.', + 'error_template_name' => 'Please enter a name to save the template.', + 'success_title' => 'Success', + 'success_body' => 'Email sent to :count user(s).', + 'template_saved_title' => 'Template saved', + 'template_saved_body' => 'The template « :name » has been saved.', + 'template_deleted_title' => 'Template deleted', + 'template_deleted_body' => 'The template « :name » has been deleted.', + ], + ], + 'settings' => [ + 'title' => 'Settings', + 'sections' => [ + 'main' => 'Settings', + 'employed_tutor_registration' => "Registration opening date for employed tutors", + 'tutor_registration' => "Registration opening date for volunteer tutors", + 'tutee_registration' => "Registration opening date for tutees", + 'cancellation_delay' => "Cancellation deadline for slots", + 'max_student_per_tutor' => 'Maximum number of students per tutor', + 'maxStudentFor1Tutor' => 'Maximum number of students for 1 tutor', + 'maxStudentFor2Tutors' => 'Maximum number of students for 2 tutors', + 'uv_catalog' => 'Course Catalog', + ], + 'fields' => [ + 'day' => 'Day', + 'time' => 'Time', + 'one_day_before' => 'Limit to "the day before" only', + 'time_before' => 'Maximum time before the slot to register or unregister', + 'code' => 'Course', + 'intitule' => 'Title', + ], + 'days' => [ + 'monday' => 'Monday', + 'tuesday' => 'Tuesday', + 'wednesday' => 'Wednesday', + 'thursday' => 'Thursday', + 'friday' => 'Friday', + 'saturday' => 'Saturday', + 'sunday' => 'Sunday', + ], + 'buttons' => [ + 'save' => 'Save', + 'reset_uvs' => 'Reset Courses', + ], + 'notifications' => [ + 'settings_saved_title' => 'Settings successfully saved', + 'uvs_update_failed_title' => 'Failed to retrieve courses', + 'uvs_updated_title' => 'Courses successfully updated', + ], + 'modals' => [ + 'edit_uv_title' => 'Edit a course', + ], + ], + 'calendar_manager' => [ + 'title' => 'Semester Calendar', + 'navigation_group' => 'Management', + 'sections' => [ + 'schedule_modification' => 'Schedule Modification', + 'schedule_description' => 'Define schedule changes or holidays', + ], + 'fields' => [ + 'date_to_modify' => 'Date to modify', + 'is_holiday' => 'Holiday', + 'is_holiday_helper' => 'Check this box for a holiday', + 'day_template' => 'Day template', + 'day_template_placeholder' => 'Select a day', + ], + 'days' => [ + 'monday' => 'Monday', + 'tuesday' => 'Tuesday', + 'wednesday' => 'Wednesday', + 'thursday' => 'Thursday', + 'friday' => 'Friday', + 'saturday' => 'Saturday', + 'sunday' => 'Sunday', + ], + 'buttons' => [ + 'save' => 'Save', + 'delete' => 'Delete', + 'previous_month' => 'Previous month', + 'next_month' => 'Next month', + ], + 'notifications' => [ + 'override_saved_title' => 'Modification saved', + 'override_saved_body' => 'The schedule modification has been saved.', + 'override_deleted_title' => 'Modification deleted', + 'override_deleted_body' => 'The schedule modification has been deleted.', + 'no_active_semester' => 'No active semester', + 'no_active_semester_message' => "There is no active semester. Please activate one in the semester settings.", + ], + ], + 'help' => [ + 'title' => 'Guide', + ], + ], +]; diff --git a/resources/lang/fr/auth.php b/resources/lang/fr/auth.php new file mode 100644 index 0000000..f86daaf --- /dev/null +++ b/resources/lang/fr/auth.php @@ -0,0 +1,7 @@ + "Ce compte n'existe pas ou le mot de passe est incorrect.", + 'password' => "Ce compte n'existe pas ou le mot de passe est incorrect.", + 'throttle' => 'Trop de tentatives de connexion. Veuillez réessayer dans :seconds secondes.', +]; diff --git a/resources/lang/fr/pages.php b/resources/lang/fr/pages.php new file mode 100644 index 0000000..4a36c02 --- /dev/null +++ b/resources/lang/fr/pages.php @@ -0,0 +1,76 @@ + [ + 'tutor_rejected' => [ + 'title' => 'Votre candidature a été refusée.', + 'message' => 'Vous pouvez modifier votre demande et la soumettre à nouveau pour une nouvelle évaluation.', + ], + ], + 'tables' => [ + 'columns' => [ + 'semaines_heures' => [ + 'semaine' => 'Semaine', + 'no_results' => 'Toutes les heures sur ce mois ont déjà été saisies.', + ], + 'total_heures' => [ + 'total' => 'Total', + ], + ], + ], + 'become_tutor' => [ + // This page doesn't have any text that needs translation + ], + 'calendar_manager' => [ + 'previous_month' => 'Mois précédent', + 'next_month' => 'Mois suivant', + 'holiday' => 'Férié', + 'navigation_label' => 'Calendrier du semestre', + 'title' => 'Calendrier du semestre', + 'selected_date' => 'Date à modifier', + 'schedule_modification' => 'Modification du planning', + 'schedule_description' => 'Définissez les modifications d\'emploi du temps ou les jours fériés', + 'holiday_help_text' => 'Cochez cette case pour un jour férié', + 'day_template' => 'Modèle de journée', + 'days' => [ + 'monday' => 'Lundi', + 'tuesday' => 'Mardi', + 'wednesday' => 'Mercredi', + 'thursday' => 'Jeudi', + 'friday' => 'Vendredi', + 'saturday' => 'Samedi', + 'sunday' => 'Dimanche', + ], + 'select_day' => 'Sélectionnez un jour', + 'save' => 'Enregistrer', + 'delete' => 'Supprimer', + 'date_out_of_range' => 'Date hors du semestre actif', + 'date_must_be_between' => 'La date sélectionnée doit être entre :start et :end', + 'modification_saved' => 'Modification enregistrée', + 'modification_deleted' => 'Modification supprimée', + ], + 'send_email' => [ + 'preview' => 'Aperçu du mail', + 'send' => 'Envoyer le mail', + 'save_template' => 'Enregistrer comme template', + 'delete_template' => 'Supprimer le template', + 'close' => 'Fermer', + ], + 'settings_page' => [ + // This page doesn't have any text that needs translation + ], + 'tutor_manage_uvs' => [ + 'add' => 'Ajouter', + 'update_languages' => 'Mettre à jour mes langues', + ], + 'rgpd' => [ + 'title' => 'Consentement RGPD', + 'data_collection' => 'Pour permettre le bon fonctionnement de l\'application, nous collectons certaines de vos données personnelles :', + 'optional_data' => 'De manière optionnelle pour les tuteur.ice.s :', + 'data_storage' => 'Ces données sont stockées de façon sécurisée sur les serveurs de l\'UTC et peuvent potentiellement être exportées par le Bureau de Vie Etudiante afin d\'améliorer Tut\'ut', + 'accept' => 'J\'accepte', + 'language' => 'Langue', + 'french' => 'Français', + 'english' => 'Anglais', + ], +]; \ No newline at end of file diff --git a/resources/lang/fr/passwords.php b/resources/lang/fr/passwords.php new file mode 100644 index 0000000..d3d79b2 --- /dev/null +++ b/resources/lang/fr/passwords.php @@ -0,0 +1,9 @@ + 'Votre mot de passe a été réinitialisé !', + 'sent' => 'Nous vous avons envoyé par e-mail le lien de réinitialisation du mot de passe !', + 'throttled' => 'Veuillez patienter avant de réessayer.', + 'token' => 'Ce jeton de réinitialisation de mot de passe est invalide.', + 'user' => "Nous ne trouvons aucun utilisateur avec cette adresse e-mail.", +]; diff --git a/resources/lang/fr/resources.php b/resources/lang/fr/resources.php new file mode 100644 index 0000000..8ef39ef --- /dev/null +++ b/resources/lang/fr/resources.php @@ -0,0 +1,534 @@ + [ + 'label' => 'gestion Comptes', + 'plural_label' => 'Gestion Comptes', + 'create_label' => 'Créer un compte', + 'fields' => [ + 'email' => 'Adresse Email', + 'role' => 'Rôle', + ], + 'roles' => [ + 'administrator' => 'Administrateur', + 'employed_tutor' => 'Tuteur Employé', + 'employed_privileged_tutor' => 'Tuteur Employé Privilégié', + 'tutor' => 'Tuteur', + 'tutee' => 'Tutoré', + ], + 'actions' => [ + 'delete' => 'Supprimer les droits', + 'upgrade' => 'Améliorer', + 'downgrade' => 'Rétrograder', + ], + ], + 'common' => [ + 'navigation' => [ + 'tutorat' => 'Tutorat', + ], + 'fields' => [ + 'salle' => 'Salle', + 'semaine' => 'Semaine', + 'tuteur1' => 'Tuteur 1', + 'tuteur2' => 'Tuteur 2', + 'uvs_proposees' => 'UVs proposées', + 'uvs_souhaitees' => 'UVs souhaitées', + 'jour_et_horaire' => 'Jour et horaire', + 'email' => 'Email', + 'password' => 'Mot de passe', + 'remember_me' => 'Se souvenir de moi', + 'start' => 'Horaire', + 'date_debut' => 'Date de début', + 'date_fin' => 'Date de fin', + 'places' => 'Places', + ], + 'buttons' => [ + 's_inscrire' => "S'inscrire", + 'se_desinscrire' => 'Se désinscrire', + 'shotgun_1' => 'Shotgun 1', + 'shotgun_2' => 'Shotgun 2', + 'login' => 'Connexion', + 'logout' => 'Déconnexion', + 'register' => 'S\'inscrire', + 'save' => 'Enregistrer', + 'cancel' => 'Annuler', + 'close' => 'Fermer', + 'create' => 'Créer', + 'edit' => 'Éditer', + 'delete' => 'Supprimer', + 'view_registrations' => 'Voir les inscrit·e·s', + 'export_excel' => 'Exporter vers Excel', + ], + 'placeholders' => [ + 'none' => '—', + ], + 'format' => [ + 'semaine_numero' => 'Semaine :number', + ], + ], + 'creneau' => [ + 'label' => 'Créneau', + 'plural_label' => 'Créneaux', + 'navigation_label' => 'Shotgun Créneaux', + ], + 'inscription_creneau' => [ + 'label' => 'Inscription Créneau', + 'plural_label' => 'Inscriptions Créneaux', + 'navigation_label' => 'Inscriptions Tutorat', + 'semaine' => 'Semaine', + 'semaine_actuelle' => 'Semaine actuelle', + 'semaine_prochaine' => 'Semaine prochaine', + 'modal_heading' => 'Liste des inscrit.e.s', + ], + 'login' => [ + 'title' => 'Connexion', + 'subtitle' => 'Connectez-vous à votre compte', + 'forgot_password' => 'Mot de passe oublié?', + ], + 'become_tutor' => [ + 'navigation_label' => 'Devenir Tuteur.ice', + 'section_title' => 'Candidature Tuteur.ice', + 'section_description' => 'Remplissez ce formulaire pour devenir tuteur.ice', + 'title' => 'Devenir Tuteur.ice', + 'fields' => [ + 'firstName' => 'Prénom', + 'lastName' => 'Nom', + 'email' => 'Email', + 'semester' => 'Semestre (ex: TC03)', + 'semester_helper' => 'Format: XXNN (ex: TC03, GI02)', + 'motivation' => 'Motivation', + 'motivation_placeholder' => 'Décrivez pourquoi vous souhaitez devenir tuteur.ice et quelles sont vos expériences pertinentes', + 'uvs' => 'Matières validées', + 'uvs_helper' => 'Sélectionnez les matières que vous pouvez enseigner', + ], + 'actions' => [ + 'save' => 'Enregistrer', + 'delete' => 'Supprimer la demande', + 'delete_modal_title' => 'Supprimer la candidature', + 'delete_modal_description' => 'Êtes-vous sûr de vouloir supprimer votre candidature ?', + ], + 'notifications' => [ + 'deleted_title' => 'Candidature supprimée', + 'deleted_body' => 'Votre candidature a été supprimée avec succès.', + 'submitted_title' => 'Candidature soumise avec succès', + 'submitted_body' => 'Votre demande pour devenir tuteur.ice a été enregistrée et est en attente d\'approbation.', + ], + ], + 'comptabilite' => [ + 'label' => 'Comptabilité', + 'plural_label' => 'Comptabilité', + 'fields' => [ + 'comment_bve' => 'Commentaire BVE', + 'month' => 'Mois', + 'tutor' => 'Tuteur', + 'validated' => 'Validé', + 'semaine_heures' => 'Semaines et heures', + 'total' => 'Total', + 'heures_supp' => 'Heures supplémentaires', + ], + 'actions' => [ + 'modify' => 'Modifier', + ], + 'notifications' => [ + 'validated_title' => 'Validation effectuée', + 'validated_body' => 'Les heures ont été validées avec succès.', + ], + 'placeholders' => [ + 'no_active_semester' => 'Pas de semestre actif', + ], + 'filters' => [ + 'not_validated' => 'Non validés', + ], + ], + 'comptabilite_tutor' => [ + 'label' => 'Comptabilité', + 'plural_label' => 'Comptabilité', + 'fields' => [ + 'only_unaccounted' => 'Afficher uniquement les créneaux non comptés', + 'heures_supp' => 'Heures supplémentaires', + 'duree' => 'Durée (heures)', + 'semaine' => 'Semaine', + 'heures_comptabilisees' => 'Heures comptabilisées', + ], + 'actions' => [ + 'confirm_hours' => 'Confirmer les heures', + 'cancel' => 'Annuler', + 'save' => 'Enregistrer', + ], + 'notifications' => [ + 'hours_updated' => 'Heures mises à jour', + ], + 'subheadings' => [ + 'save_reminder' => 'Pensez à enregistrer avant de quitter cette page', + ], + ], + 'components' => [ + 'slot_creneau' => [ + 'inscrits' => ':count inscrit·e·s', + 'present' => 'Présent·e', + 'absent' => 'Absent·e', + ], + 'no_creneaux' => [ + 'title' => 'Aucun créneau trouvé', + 'description' => 'Vous n\'avez pas de créneaux à valider pour cette semaine.', + ], + ], + 'admin' => [ + 'navigation_group' => [ + 'administration' => 'Administration', + 'gestion' => 'Gestion', + ], + 'salle' => [ + 'label' => 'Salle', + 'plural_label' => 'Salles', + 'fields' => [ + 'numero' => 'Numéro de salle', + 'disponibilites' => 'Disponibilités', + 'heure_debut' => 'Heure de début', + 'heure_fin' => 'Heure de fin', + ], + 'jours' => [ + 'lundi' => 'Lundi', + 'mardi' => 'Mardi', + 'mercredi' => 'Mercredi', + 'jeudi' => 'Jeudi', + 'vendredi' => 'Vendredi', + 'samedi' => 'Samedi', + 'medians' => 'Médians', + 'finaux' => 'Finaux', + ], + ], + 'semaine' => [ + 'label' => 'Semaine', + 'plural_label' => 'Semaines', + 'fields' => [ + 'numero' => 'Numéro', + 'fk_semestre' => 'Semestre', + 'date_debut' => 'Date de début', + 'date_fin' => 'Date de fin', + 'is_vacances' => 'Vacances', + 'helperText_is_vacances' => 'Aucune génération de créneaux ne sera faite cette semaine', + ], + 'filters' => [ + 'future' => 'Semaines futures', + ], + 'actions' => [ + 'creer_prochaine' => 'Créer la prochaine semaine', + 'generer_creneaux' => '(Re)Générer les créneaux', + 'vacances' => 'Vacances', + ], + 'messages' => [ + 'pas_generation_vacances' => 'Pas de génération de créneaux pour les semaines de vacances.', + 'creneaux_generes' => 'Créneaux générés avec succès pour la semaine :numero.', + ], + ], + 'semestre' => [ + 'label' => 'Semestre', + 'plural_label' => 'Semestres', + 'fields' => [ + 'code' => 'Code', + 'is_active' => 'Actif', + 'debut' => 'Date de début', + 'fin' => 'Date de fin', + 'debut_medians' => 'Début médians', + 'fin_medians' => 'Fin médians', + 'debut_finaux' => 'Début finaux', + 'fin_finaux' => 'Fin finaux', + ], + 'actions' => [ + 'activer' => 'Activer', + ], + 'values' => [ + 'oui' => 'Oui', + 'non' => 'Non', + ], + ], + 'comptabilite' => [ + 'label' => 'Comptabilité', + 'plural_label' => 'Comptabilité', + 'fields' => [ + 'commentaire_bve' => 'Commentaire BVE', + 'no_semestre_actif' => 'Pas de semestre actif', + 'mois' => 'Mois', + 'tuteur' => 'Tuteur', + 'valide' => 'Validé', + 'semaines_heures' => 'Semaines et heures', + 'total' => 'Total', + 'heures_supplementaires' => 'Heures supplémentaires', + 'nombre_heures_supplementaires' => 'Nombre d\'heures supplémentaires', + 'justification' => 'Justification', + ], + 'filters' => [ + 'non_valides' => 'Non validés', + ], + 'actions' => [ + 'modifier' => 'Modifier', + 'valider' => 'Valider', + 'annuler' => 'Annuler', + 'commentaire' => 'Commentaire', + ], + 'modals' => [ + 'valider_heures' => 'Valider les heures', + 'valider_confirmation' => 'Voulez-vous valider les heures de :name ?', + 'valider_oui' => 'Oui, valider', + 'annuler_heures' => 'Annuler la validation des heures', + 'annuler_confirmation' => 'Voulez-vous annuler la validation des heures de :name ?', + 'annuler_oui' => 'Oui, annuler', + ], + ], + ], + 'feedback' => [ + 'label' => 'Feedback', + 'plural_label' => 'Feedbacks', + 'tutee_label' => 'Mes Feedbacks', + 'fields' => [ + 'text' => 'Donnez-nous votre avis !', + ], + 'actions' => [ + 'delete' => [ + 'modal_heading' => 'Supprimer le feedback', + 'modal_subheading' => 'Êtes-vous sûr de vouloir supprimer ce feedback ?', + 'modal_button' => 'Supprimer', + ], + ], + ], + 'tutor_application' => [ + 'label' => 'Candidature Tuteur.ice', + 'plural_label' => 'Candidatures Tuteur.ice', + 'navigation_group' => 'Gestion', + 'fields' => [ + 'firstname' => 'Prénom', + 'lastname' => 'Nom', + 'semester' => 'Semestre', + 'status' => 'Status', + ], + 'status' => [ + 'pending' => 'En attente', + 'rejected' => 'Refusé', + ], + 'filters' => [ + 'semester' => 'Semestre', + 'pending' => 'En attente uniquement', + ], + 'actions' => [ + 'view' => [ + 'label' => 'Plus de détails', + 'modal_heading' => 'Détails de la candidature', + 'modal_cancel_label' => 'Fermer', + ], + 'accept' => [ + 'label' => 'Accepter', + 'modal_heading' => 'Confirmer l\'acceptation', + 'modal_description' => 'Êtes-vous sûr de vouloir accepter cette candidature ?', + 'notification_title' => 'Candidature acceptée', + ], + 'reject' => [ + 'label' => 'Refuser', + 'modal_heading' => 'Confirmer le refus', + 'modal_description' => 'Êtes-vous sûr de vouloir refuser cette candidature ?', + 'notification_title' => 'Candidature refusée', + ], + ], + 'sections' => [ + 'personal_info' => 'Informations Personnelles', + 'uvs' => 'Matières Proposées', + 'motivation' => 'Motivation', + ], + ], + 'widgets' => [ + 'admin' => [ + 'stats' => [ + 'avg_tutees_per_night' => 'Moyenne de tutoré.e.s / soir', + 'avg_tutees_per_slot' => 'Moyenne de tutoré.e.s / créneau', + 'total_active_tutees' => 'Nombre total de tutoré.e.s actif.ve sur le semestre', + 'open_slots_per_week' => 'Créneaux ouverts / semaine', + 'volunteer_tutors' => 'Tuteurs bénévoles', + 'total_hours' => 'Heures données (comptabilisées)', + 'most_requested_courses' => 'UVs les plus demandées', + ], + ], + 'tutee_creneaux' => [ + 'heading' => 'Mes prochaines inscriptions tutoré.e', + 'columns' => [ + 'day' => 'Jour', + 'schedule' => 'Horaire', + 'room' => 'Salle', + 'tutor1' => 'Tuteur 1', + 'tutor2' => 'Tuteur 2', + 'requested_courses' => 'UVs demandées', + ], + ], + 'tutor_creneaux' => [ + 'heading' => 'Mes prochains créneaux tuteur.ice', + 'columns' => [ + 'day' => 'Jour', + 'schedule' => 'Horaire', + 'room' => 'Salle', + 'tutor1' => 'Tuteur 1', + 'tutor2' => 'Tuteur 2', + 'registrations_count' => 'Nombre d\'inscrits', + 'requested_courses' => 'UVs demandées', + ], + ], + ], + 'pages' => [ + 'tutor_manage_uvs' => [ + 'title' => 'Mes UVs proposées', + 'navigation_group' => 'Tutorat', + 'sections' => [ + 'propose_uv' => [ + 'title' => 'Proposer une UV', + 'description' => "Si vous ne trouvez pas l'UV que vous cherchez, vous pouvez demander à :tutors de l'ajouter", + ], + 'create_new_uv' => [ + 'title' => 'OU créer une nouvelle UV', + 'description' => 'Créez une nouvelle UV avec son code et son intitulé', + ], + ], + 'fields' => [ + 'languages' => 'Langues parlées', + 'selected_codes' => 'UV existante', + 'code' => "Code de l'UV", + 'intitule' => "Intitulé de l'UV", + ], + 'notifications' => [ + 'languages_updated_title' => 'Langues mises à jour', + 'languages_updated_body' => 'Vos langues ont été mises à jour avec succès.', + 'uvs_added_title' => 'UV(s) ajoutée(s)', + 'uvs_added_body' => 'Vos UVs ont été mises à jour avec succès.', + 'uvs_created_title' => 'UV(s) ajoutée(s)', + 'uvs_created_body' => "L'UV a été créé et vos UVs ont été mises à jour avec succès.", + ], + 'languages' => [ + 'en' => '🇬🇧 Anglais', + 'es' => '🇪🇸 Espagnol', + 'zh' => '🇨🇳 Chinois', + 'de' => '🇩🇪 Allemand', + 'ar' => '🇸🇦 Arabe', + 'ru' => '🇷🇺 Russe', + 'ja' => '🇯🇵 Japonais', + 'it' => '🇮🇹 Italien', + ], + ], + 'send_email' => [ + 'title' => 'Envoyer un mail', + 'navigation_group' => 'Gestion', + 'fields' => [ + 'template' => 'Charger un template', + 'roles' => 'Destinataires', + 'mail_title' => 'Sujet', + 'content' => 'Contenu', + 'template_name' => 'Nom du template', + 'template_name_helper' => "Requis uniquement pour l'enregistrement", + ], + 'roles' => [ + 'administrator' => 'Administrateur', + 'employed_privileged_tutor' => 'Tuteur.ice employé.e privilégié.e', + 'employed_tutor' => 'Tuteur.ice employé.e', + 'tutor' => 'Tuteur.ice', + 'tutee' => 'Tutoré.e', + ], + 'buttons' => [ + 'preview' => 'Aperçu', + 'send' => 'Envoyer', + 'save_template' => 'Enregistrer comme template', + 'delete_template' => 'Supprimer le template', + ], + 'notifications' => [ + 'error_title' => 'Erreur', + 'error_select_role' => 'Veuillez sélectionner au moins un rôle.', + 'error_template_name' => 'Veuillez entrer un nom pour enregistrer le template.', + 'success_title' => 'Succès', + 'success_body' => 'Email envoyé à :count utilisateur(s).', + 'template_saved_title' => 'Template enregistré', + 'template_saved_body' => 'Le template « :name » a été sauvegardé.', + 'template_deleted_title' => 'Template supprimé', + 'template_deleted_body' => 'Le template « :name » a été supprimé.', + ], + ], + 'settings' => [ + 'title' => 'Paramètres', + 'sections' => [ + 'main' => 'Paramètres', + 'employed_tutor_registration' => "Date d'ouverture des inscriptions pour les tuteur.ice.s employé.e.s", + 'tutor_registration' => "Date d'ouverture des inscriptions pour les tuteur.ice.s bénévoles", + 'tutee_registration' => "Date d'ouverture des inscriptions pour les tutoré.e.s", + 'cancellation_delay' => "Délai d'annulation des créneaux", + 'max_student_per_tutor' => "Nombre maximum d'élèves par tuteur", + 'uv_catalog' => 'Catalogue des UVs', + ], + 'fields' => [ + 'day' => 'Jour', + 'time' => 'Heure', + 'one_day_before' => 'Limiter à "la veille" uniquement', + 'time_before' => 'Délai maximal avant un créneau pour s\'inscrire ou se désinscrire', + 'max_student_for_1_tutor' => "Nombre maximum d'élèves pour 1 tuteur", + 'max_student_for_2_tutors' => "Nombre maximum d'élèves pour 2 tuteurs", + 'code' => 'UV', + 'intitule' => 'Intitulé', + ], + 'days' => [ + 'monday' => 'Lundi', + 'tuesday' => 'Mardi', + 'wednesday' => 'Mercredi', + 'thursday' => 'Jeudi', + 'friday' => 'Vendredi', + 'saturday' => 'Samedi', + 'sunday' => 'Dimanche', + ], + 'buttons' => [ + 'save' => 'Enregistrer', + 'reset_uvs' => 'Reset les UVs', + ], + 'notifications' => [ + 'settings_saved_title' => 'Paramètres sauvegardés avec succès', + 'uvs_update_failed_title' => 'Échec de la récupération des UVs', + 'uvs_updated_title' => 'UVs mises à jour avec succès', + ], + 'modals' => [ + 'edit_uv_title' => 'Modifier une UV', + ], + ], + 'calendar_manager' => [ + 'title' => 'Calendrier du semestre', + 'navigation_group' => 'Gestion', + 'sections' => [ + 'schedule_modification' => 'Modification du planning', + 'schedule_description' => 'Définissez les modifications d\'emploi du temps ou les jours fériés', + ], + 'fields' => [ + 'date_to_modify' => 'Date à modifier', + 'is_holiday' => 'Jour férié', + 'is_holiday_helper' => 'Cochez cette case pour un jour férié', + 'day_template' => 'Modèle de journée', + 'day_template_placeholder' => 'Sélectionnez un jour', + ], + 'days' => [ + 'monday' => 'Lundi', + 'tuesday' => 'Mardi', + 'wednesday' => 'Mercredi', + 'thursday' => 'Jeudi', + 'friday' => 'Vendredi', + 'saturday' => 'Samedi', + 'sunday' => 'Dimanche', + ], + 'buttons' => [ + 'save' => 'Enregistrer', + 'delete' => 'Supprimer', + 'previous_month' => 'Mois précédent', + 'next_month' => 'Mois suivant', + ], + 'notifications' => [ + 'override_saved_title' => 'Modification enregistrée', + 'override_saved_body' => 'La modification du planning a été enregistrée.', + 'override_deleted_title' => 'Modification supprimée', + 'override_deleted_body' => 'La modification du planning a été supprimée.', + 'no_active_semester' => 'Pas de semestre actif', + 'no_active_semester_message' => "Il n'y a pas de semestre actif. Veuillez en activer un dans les paramètres de semestre.", + ], + ], + 'help' => [ + 'title' => 'Guide', + ], + ], +]; diff --git a/resources/lang/fr/validation.php b/resources/lang/fr/validation.php new file mode 100644 index 0000000..8b14179 --- /dev/null +++ b/resources/lang/fr/validation.php @@ -0,0 +1,5 @@ + 'Un compte est déjà associé à cette adresse e-mail.', +]; diff --git a/resources/views/filament/pages/register.blade.php b/resources/views/filament/pages/register.blade.php new file mode 100644 index 0000000..e283904 --- /dev/null +++ b/resources/views/filament/pages/register.blade.php @@ -0,0 +1,7 @@ + + {{ $this->form }} + + + S'inscrire + + \ No newline at end of file diff --git a/resources/views/vendor/mail/html/button.blade.php b/resources/views/vendor/mail/html/button.blade.php new file mode 100644 index 0000000..4a9bf7d --- /dev/null +++ b/resources/views/vendor/mail/html/button.blade.php @@ -0,0 +1,24 @@ +@props([ + 'url', + 'color' => 'primary', + 'align' => 'center', +]) + + + + + diff --git a/resources/views/vendor/mail/html/footer.blade.php b/resources/views/vendor/mail/html/footer.blade.php new file mode 100644 index 0000000..3ff41f8 --- /dev/null +++ b/resources/views/vendor/mail/html/footer.blade.php @@ -0,0 +1,11 @@ + + + + + + + + + diff --git a/resources/views/vendor/mail/html/header.blade.php b/resources/views/vendor/mail/html/header.blade.php new file mode 100644 index 0000000..56197f8 --- /dev/null +++ b/resources/views/vendor/mail/html/header.blade.php @@ -0,0 +1,12 @@ +@props(['url']) + + + +@if (trim($slot) === 'Laravel') + +@else +{{ $slot }} +@endif + + + diff --git a/resources/views/vendor/mail/html/layout.blade.php b/resources/views/vendor/mail/html/layout.blade.php new file mode 100644 index 0000000..d31a01d --- /dev/null +++ b/resources/views/vendor/mail/html/layout.blade.php @@ -0,0 +1,58 @@ + + + +{{ config('app.name') }} + + + + + +{{ $head ?? '' }} + + + + + + + + + + diff --git a/resources/views/vendor/mail/html/message.blade.php b/resources/views/vendor/mail/html/message.blade.php new file mode 100644 index 0000000..67f40a0 --- /dev/null +++ b/resources/views/vendor/mail/html/message.blade.php @@ -0,0 +1,26 @@ + + {{-- Header --}} + + + Message de Tut'ut ! + + + + {{-- Body --}} + {{ $slot }} + + {{-- Subcopy --}} + @isset($subcopy) + + + {{ $subcopy }} + + + @endisset + + {{-- Footer --}} + + + + + diff --git a/resources/views/vendor/mail/html/panel.blade.php b/resources/views/vendor/mail/html/panel.blade.php new file mode 100644 index 0000000..2975a60 --- /dev/null +++ b/resources/views/vendor/mail/html/panel.blade.php @@ -0,0 +1,14 @@ + + + + + + diff --git a/resources/views/vendor/mail/html/subcopy.blade.php b/resources/views/vendor/mail/html/subcopy.blade.php new file mode 100644 index 0000000..790ce6c --- /dev/null +++ b/resources/views/vendor/mail/html/subcopy.blade.php @@ -0,0 +1,7 @@ + + + + + diff --git a/resources/views/vendor/mail/html/table.blade.php b/resources/views/vendor/mail/html/table.blade.php new file mode 100644 index 0000000..a5f3348 --- /dev/null +++ b/resources/views/vendor/mail/html/table.blade.php @@ -0,0 +1,3 @@ +
+{{ Illuminate\Mail\Markdown::parse($slot) }} +
diff --git a/resources/views/vendor/mail/html/themes/default.css b/resources/views/vendor/mail/html/themes/default.css new file mode 100644 index 0000000..b64dc60 --- /dev/null +++ b/resources/views/vendor/mail/html/themes/default.css @@ -0,0 +1,295 @@ +/* Base */ + +body, +body *:not(html):not(style):not(br):not(tr):not(code) { + box-sizing: border-box; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif, + 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol'; + position: relative; +} + +body { + -webkit-text-size-adjust: none; + background-color: #ffffff; + color: #718096; + height: 100%; + line-height: 1.4; + margin: 0; + padding: 0; + width: 100% !important; +} + +p, +ul, +ol, +blockquote { + line-height: 1.4; + text-align: left; +} + +a { + color: #3869d4; +} + +a img { + border: none; +} + +/* Typography */ + +h1 { + color: #3d4852; + font-size: 18px; + font-weight: bold; + margin-top: 0; + text-align: left; +} + +h2 { + font-size: 16px; + font-weight: bold; + margin-top: 0; + text-align: left; +} + +h3 { + font-size: 14px; + font-weight: bold; + margin-top: 0; + text-align: left; +} + +p { + font-size: 16px; + line-height: 1.5em; + margin-top: 0; + text-align: left; +} + +p.sub { + font-size: 12px; +} + +img { + max-width: 100%; +} + +/* Layout */ + +.wrapper { + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 100%; + background-color: #edf2f7; + margin: 0; + padding: 0; + width: 100%; +} + +.content { + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 100%; + margin: 0; + padding: 0; + width: 100%; +} + +/* Header */ + +.header { + padding: 25px 0; + text-align: center; +} + +.header a { + color: #3d4852; + font-size: 19px; + font-weight: bold; + text-decoration: none; +} + +/* Logo */ + +.logo { + height: 75px; + max-height: 75px; + width: 75px; +} + +/* Body */ + +.body { + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 100%; + background-color: #edf2f7; + border-bottom: 1px solid #edf2f7; + border-top: 1px solid #edf2f7; + margin: 0; + padding: 0; + width: 100%; +} + +.inner-body { + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 570px; + background-color: #ffffff; + border-color: #e8e5ef; + border-radius: 2px; + border-width: 1px; + box-shadow: 0 2px 0 rgba(0, 0, 150, 0.025), 2px 4px 0 rgba(0, 0, 150, 0.015); + margin: 0 auto; + padding: 0; + width: 570px; +} + +.inner-body a { + word-break: break-all; +} + +/* Subcopy */ + +.subcopy { + border-top: 1px solid #e8e5ef; + margin-top: 25px; + padding-top: 25px; +} + +.subcopy p { + font-size: 14px; +} + +/* Footer */ + +.footer { + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 570px; + margin: 0 auto; + padding: 0; + text-align: center; + width: 570px; +} + +.footer p { + color: #b0adc5; + font-size: 12px; + text-align: center; +} + +.footer a { + color: #b0adc5; + text-decoration: underline; +} + +/* Tables */ + +.table table { + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 100%; + margin: 30px auto; + width: 100%; +} + +.table th { + border-bottom: 1px solid #edeff2; + margin: 0; + padding-bottom: 8px; +} + +.table td { + color: #74787e; + font-size: 15px; + line-height: 18px; + margin: 0; + padding: 10px 0; +} + +.content-cell { + max-width: 100vw; + padding: 32px; +} + +/* Buttons */ + +.action { + -premailer-cellpadding: 0; + -premailer-cellspacing: 0; + -premailer-width: 100%; + margin: 30px auto; + padding: 0; + text-align: center; + width: 100%; + float: unset; +} + +.button { + -webkit-text-size-adjust: none; + border-radius: 4px; + color: #fff; + display: inline-block; + overflow: hidden; + text-decoration: none; +} + +.button-blue, +.button-primary { + background-color: #2d3748; + border-bottom: 8px solid #2d3748; + border-left: 18px solid #2d3748; + border-right: 18px solid #2d3748; + border-top: 8px solid #2d3748; +} + +.button-green, +.button-success { + background-color: #48bb78; + border-bottom: 8px solid #48bb78; + border-left: 18px solid #48bb78; + border-right: 18px solid #48bb78; + border-top: 8px solid #48bb78; +} + +.button-red, +.button-error { + background-color: #e53e3e; + border-bottom: 8px solid #e53e3e; + border-left: 18px solid #e53e3e; + border-right: 18px solid #e53e3e; + border-top: 8px solid #e53e3e; +} + +/* Panels */ + +.panel { + border-left: #2d3748 solid 4px; + margin: 21px 0; +} + +.panel-content { + background-color: #edf2f7; + color: #718096; + padding: 16px; +} + +.panel-content p { + color: #718096; +} + +.panel-item { + padding: 0; +} + +.panel-item p:last-of-type { + margin-bottom: 0; + padding-bottom: 0; +} + +/* Utilities */ + +.break-all { + word-break: break-all; +} diff --git a/resources/views/vendor/mail/text/button.blade.php b/resources/views/vendor/mail/text/button.blade.php new file mode 100644 index 0000000..97444eb --- /dev/null +++ b/resources/views/vendor/mail/text/button.blade.php @@ -0,0 +1 @@ +{{ $slot }}: {{ $url }} diff --git a/resources/views/vendor/mail/text/footer.blade.php b/resources/views/vendor/mail/text/footer.blade.php new file mode 100644 index 0000000..3338f62 --- /dev/null +++ b/resources/views/vendor/mail/text/footer.blade.php @@ -0,0 +1 @@ +{{ $slot }} diff --git a/resources/views/vendor/mail/text/header.blade.php b/resources/views/vendor/mail/text/header.blade.php new file mode 100644 index 0000000..97444eb --- /dev/null +++ b/resources/views/vendor/mail/text/header.blade.php @@ -0,0 +1 @@ +{{ $slot }}: {{ $url }} diff --git a/resources/views/vendor/mail/text/layout.blade.php b/resources/views/vendor/mail/text/layout.blade.php new file mode 100644 index 0000000..ec58e83 --- /dev/null +++ b/resources/views/vendor/mail/text/layout.blade.php @@ -0,0 +1,9 @@ +{!! strip_tags($header ?? '') !!} + +{!! strip_tags($slot) !!} +@isset($subcopy) + +{!! strip_tags($subcopy) !!} +@endisset + +{!! strip_tags($footer ?? '') !!} diff --git a/resources/views/vendor/mail/text/message.blade.php b/resources/views/vendor/mail/text/message.blade.php new file mode 100644 index 0000000..67f40a0 --- /dev/null +++ b/resources/views/vendor/mail/text/message.blade.php @@ -0,0 +1,26 @@ + + {{-- Header --}} + + + Message de Tut'ut ! + + + + {{-- Body --}} + {{ $slot }} + + {{-- Subcopy --}} + @isset($subcopy) + + + {{ $subcopy }} + + + @endisset + + {{-- Footer --}} + + + + + diff --git a/resources/views/vendor/mail/text/panel.blade.php b/resources/views/vendor/mail/text/panel.blade.php new file mode 100644 index 0000000..3338f62 --- /dev/null +++ b/resources/views/vendor/mail/text/panel.blade.php @@ -0,0 +1 @@ +{{ $slot }} diff --git a/resources/views/vendor/mail/text/subcopy.blade.php b/resources/views/vendor/mail/text/subcopy.blade.php new file mode 100644 index 0000000..3338f62 --- /dev/null +++ b/resources/views/vendor/mail/text/subcopy.blade.php @@ -0,0 +1 @@ +{{ $slot }} diff --git a/resources/views/vendor/mail/text/table.blade.php b/resources/views/vendor/mail/text/table.blade.php new file mode 100644 index 0000000..3338f62 --- /dev/null +++ b/resources/views/vendor/mail/text/table.blade.php @@ -0,0 +1 @@ +{{ $slot }} diff --git a/resources/views/vendor/notifications/email.blade.php b/resources/views/vendor/notifications/email.blade.php new file mode 100644 index 0000000..65796ca --- /dev/null +++ b/resources/views/vendor/notifications/email.blade.php @@ -0,0 +1,57 @@ + +{{-- Greeting --}} +@if (! empty($greeting)) +# {{ $greeting }} +@else +@if ($level === 'error') +# @lang('Whoops!') +@else +# @lang('Hello!') +@endif +@endif + +{{-- Intro Lines --}} +@foreach ($introLines as $line) +{{ $line }} + +@endforeach + +{{-- Action Button --}} +@isset($actionText) + $level, + default => 'primary', + }; +?> + +{{ $actionText }} + +@endisset + +{{-- Outro Lines --}} +@foreach ($outroLines as $line) +{{ $line }} + +@endforeach + +{{-- Salutation --}} +@if (! empty($salutation)) +{{ $salutation }} +@else +@lang('Regards,')
+{{ config('app.name') }} +@endif + +{{-- Subcopy --}} +@isset($actionText) + +@lang( + "Si le bouton ne fonctionne pas, utilisez ce lien :", + [ + 'actionText' => $actionText, + ] +) [{{ $displayableActionUrl }}]({{ $actionUrl }}) + +@endisset +
diff --git a/routes/web.php b/routes/web.php index 50b3bd6..87f61fe 100644 --- a/routes/web.php +++ b/routes/web.php @@ -4,12 +4,14 @@ use Illuminate\Support\Facades\Route; /**************************************************** Authentification ****************************************************/ -Route::get('/login', [\App\Http\Controllers\AuthController::class, 'login'])->name('login'); -Route::get('/callback', [\App\Http\Controllers\AuthController::class, 'callback'])->name('callback'); -Route::get('/logout', [\App\Http\Controllers\AuthController::class, 'logout'])->name('logout'); +// Route::get('/login', [\App\Http\Controllers\AuthController::class, 'login'])->name('login'); +// Route::get('/callback', [\App\Http\Controllers\AuthController::class, 'callback'])->name('callback'); +// Route::get('/logout', [\App\Http\Controllers\AuthController::class, 'logout'])->name('logout'); /**************************************************************************************************************************/ /**************************************************** RGPD ****************************************************/ Route::get('/rgpd-notice', [RgpdController::class, 'show'])->name('rgpd.notice'); Route::post('/rgpd-notice', [RgpdController::class, 'accept'])->name('rgpd.accept'); -/**************************************************************************************************************************/ \ No newline at end of file +/**************************************************************************************************************************/ + +Route::get('/registration', [App\Http\Controllers\AuthController::class, 'register'])->name('registration'); \ No newline at end of file diff --git a/storage/app/.gitignore b/storage/app/.gitignore index fedb287..bab2739 100644 --- a/storage/app/.gitignore +++ b/storage/app/.gitignore @@ -1,4 +1,5 @@ * +!keys/ !private/ !public/ !.gitignore diff --git a/storage/app/keys/.gitignore b/storage/app/keys/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/storage/app/keys/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/storage/app/public/.gitignore b/storage/app/public/.gitignore index d6b7ef3..ae3c396 100644 --- a/storage/app/public/.gitignore +++ b/storage/app/public/.gitignore @@ -1,2 +1,3 @@ * +!tuto/ !.gitignore