diff --git a/.env.example b/.env.example index e0240ef..6461dd9 100644 --- a/.env.example +++ b/.env.example @@ -16,7 +16,7 @@ PHP_CLI_SERVER_WORKERS=4 BCRYPT_ROUNDS=12 LOG_CHANNEL=stack -LOG_STACK=single +LOG_STACK=daily LOG_DEPRECATIONS_CHANNEL=null LOG_LEVEL=debug @@ -86,9 +86,15 @@ ISPCONFIG_TEST_SOAP_URI= ISPCONFIG_TEST_USERNAME= ISPCONFIG_TEST_PASSWORD= +# NextCloud Server NEXTCLOUD_URL= NEXTCLOUD_USERNAME= NEXTCLOUD_PASSWORD= +#ListMonk server +LISTMONK_URL= +LISTMONK_USERNAME= +LISTMONK_PASSWORD= + #SYMPA_API_ID= #SYMPA_API_PWD= diff --git a/.lando.yml b/.lando.yml index 401c13b..3cf92df 100644 --- a/.lando.yml +++ b/.lando.yml @@ -34,6 +34,14 @@ services: type: redis:5 persist: false portforward: true + worker: + type: php:8.3 + via: cli + command: php artisan queue:work --sleep=3 --tries=3 --timeout=60 + overrides: + volumes: + - .:/app + working_dir: /app tooling: npm: diff --git a/app/Filament/Resources/Members/Schemas/MemberForm.php b/app/Filament/Resources/Members/Schemas/MemberForm.php index 316d0a1..06fc1bf 100644 --- a/app/Filament/Resources/Members/Schemas/MemberForm.php +++ b/app/Filament/Resources/Members/Schemas/MemberForm.php @@ -3,22 +3,22 @@ namespace App\Filament\Resources\Members\Schemas; use App\Enums\IspconfigType; +use App\Filament\Actions\ServiceToggleAction; use App\Models\Member; use Filament\Actions\Action; use Filament\Forms\Components\DatePicker; use Filament\Forms\Components\Select; -use Filament\Infolists\Components\ViewEntry; -use Filament\Schemas\Components\Tabs; -use Filament\Schemas\Components\Tabs\Tab; use Filament\Forms\Components\TextInput; use Filament\Forms\Components\Toggle; -use Filament\Schemas\Components\Grid; -use Filament\Schemas\Components\Section; -use Filament\Schemas\Schema; use Filament\Infolists\Components\RepeatableEntry; use Filament\Infolists\Components\TextEntry; +use Filament\Infolists\Components\ViewEntry; +use Filament\Schemas\Components\Grid; +use Filament\Schemas\Components\Section; +use Filament\Schemas\Components\Tabs; +use Filament\Schemas\Components\Tabs\Tab; +use Filament\Schemas\Schema; use Filament\Support\Icons\Heroicon; -use App\Filament\Actions\ServiceToggleAction; class MemberForm { @@ -42,10 +42,10 @@ class MemberForm | TAB : Informations générales |-------------------------------------------------------------------------- */ - Tabs\Tab::make('Informations générales') + Tabs\Tab::make(__('members.tabs.general_info')) ->icon(Heroicon::OutlinedInformationCircle) ->schema([ - Section::make('Informations personnelles') + Section::make(__('members.sections.personal_info')) ->collapsible() ->schema([ TextInput::make('lastname') @@ -64,7 +64,7 @@ class MemberForm ]) ->columns(2), - Section::make('Informations administratives') + Section::make(__('members.sections.administrative_info')) ->collapsible() ->schema([ TextInput::make('keycloak_id') @@ -86,7 +86,7 @@ class MemberForm ]) ->columns(2), - Section::make('Coordonnées') + Section::make(__('members.sections.contact_info')) ->collapsible() ->schema([ TextInput::make('email') @@ -122,143 +122,135 @@ class MemberForm | TAB : Services/Modules |-------------------------------------------------------------------------- */ - Tabs\Tab::make('Modules') + Tabs\Tab::make(__('members.tabs.modules')) ->icon(Heroicon::OutlinedPuzzlePiece) ->schema([ - /* - | Messageries ISPConfig (lecture seule) - */ - Section::make('Messagerie ISPConfig') + Section::make(__('members.sections.ispconfig_mail')) ->afterHeader([ ServiceToggleAction::forService('mail'), ]) ->collapsible() ->schema([ RepeatableEntry::make('ispconfig_mails') - ->label('Données ISPConfig Mail') - ->state(fn(?Member $record) => $record?->ispconfigs() + ->label(__('members.ispconfig.mail_data')) + ->state(fn (?Member $record) => $record?->ispconfigs() ->where('type', IspconfigType::MAIL) ->get() ) ->schema([ TextEntry::make('email') - ->label('Adresse email'), + ->label(__('members.ispconfig.email')), TextEntry::make('ispconfig_service_user_id') - ->label('ID ISPConfig'), + ->label(__('members.ispconfig.id')), TextEntry::make('data.mailuser.quota') - ->label('Quota'), - //->formatStateUsing(fn($state) => $state ? "{$state} Mo" : 'Non défini' - //), + ->label(__('members.ispconfig.quota')), TextEntry::make('data.mailuser.domain') - ->label('Domaine') + ->label(__('members.ispconfig.domain')) ->default('retzien.fr'), ViewEntry::make('data') ->label('JSON') ->view('filament.components.json-viewer') - ->viewData(fn($state) => [ + ->viewData(fn ($state) => [ 'data' => $state, ]) ->columnSpanFull(), ]) ->columns(2), ]) - ->visible(fn(?Member $record) => $record?->ispconfigs() + ->visible(fn (?Member $record) => $record?->ispconfigs() ->where('type', IspconfigType::MAIL) ->exists() ), - /* - | Hébergements web ISPConfig - */ - Section::make('Hébergements Web') + Section::make(__('members.sections.ispconfig_web')) ->afterHeader([ ServiceToggleAction::forService('webhosting'), ]) ->collapsible() ->schema([ RepeatableEntry::make('ispconfigs_web') - ->label('Données ISPConfig Web') - ->state(fn(?Member $record) => $record?->ispconfigs() + ->label(__('members.ispconfig.web_data')) + ->state(fn (?Member $record) => $record?->ispconfigs() ->where('type', IspconfigType::WEB) ->get() - ->map(fn($ispconfig) => $ispconfig->toArray()) + ->map(fn ($ispconfig) => $ispconfig->toArray()) ->all() ) ->schema([ TextEntry::make('data.domain_id') - ->label('ID ISPConfig'), + ->label(__('members.ispconfig.id')), TextEntry::make('data.domain') - ->label('Domaine'), + ->label(__('members.ispconfig.domain')), TextEntry::make('data.active') - ->label('État') - ->formatStateUsing(fn($state) => $state === 'y' ? 'Activé' : 'Désactivé' + ->label(__('members.ispconfig.state')) + ->formatStateUsing(fn ($state) => $state === 'y' + ? __('members.ispconfig.enabled') + : __('members.ispconfig.disabled') ), ViewEntry::make('data') ->label('JSON') ->view('filament.components.json-viewer') - ->viewData(fn($state) => [ + ->viewData(fn ($state) => [ 'data' => $state, ]) ->columnSpanFull(), - // @todo: background color : #F5F8FA ]) ->columns(3), ]) - ->visible(fn(?Member $record) => $record?->ispconfigs() + ->visible(fn (?Member $record) => $record?->ispconfigs() ->where('type', IspconfigType::WEB) ->exists() ), - /* - | Compte(s) NextCloud (lecture seule) - */ - Section::make('NextCloud') + Section::make(__('members.sections.nextcloud')) ->afterHeader([ ServiceToggleAction::forService('nextcloud'), ]) ->collapsible() ->schema([ RepeatableEntry::make('nextcloud_accounts') - ->label('Données NextCloud') - ->state(fn(?Member $record) => $record?->nextcloudAccounts() + ->label(__('members.ispconfig.nextcloud_data')) + ->state(fn (?Member $record) => $record?->nextcloudAccounts() ->get() - ->map(fn($nextcloudAccount) => $nextcloudAccount->toArray()) + ->map(fn ($nextcloudAccount) => $nextcloudAccount->toArray()) ->all() ) ->schema([ TextEntry::make('nextcloud_user_id') - ->label('Id Nextcloud'), + ->label(__('members.ispconfig.nextcloud_id')), TextEntry::make('data.displayname') - ->label('Nom de l\'utilisateur'), + ->label(__('members.ispconfig.display_name')), TextEntry::make('data.enabled') - ->label('État') - ->formatStateUsing(fn($state) => $state == 'true' ? 'Activé' : 'Désactivé' + ->label(__('members.ispconfig.state')) + ->formatStateUsing(fn ($state) => $state == 'true' + ? __('members.ispconfig.enabled') + : __('members.ispconfig.disabled') ), ViewEntry::make('data') ->label('JSON') ->view('filament.components.json-viewer') - ->viewData(fn($state) => [ + ->viewData(fn ($state) => [ 'data' => $state, ]) ->columnSpanFull(), ]) ->columns(3), ]) - ->visible(fn(?Member $record) => $record?->nextcloudAccounts() + ->visible(fn (?Member $record) => $record?->nextcloudAccounts() ->exists() ), ]), ]) - ->contained(false) + ->contained(false), ]) ->columnSpan(3), @@ -269,7 +261,7 @@ class MemberForm */ Grid::make(1) ->schema([ - Section::make('Statut') + Section::make(__('members.sections.status')) ->collapsible() ->schema([ Select::make('status') @@ -290,18 +282,18 @@ class MemberForm ]) ->extraAttributes(['class' => 'sticky top-4 h-fit']), - Section::make('Actions') + Section::make(__('members.sections.actions')) ->collapsible() ->schema([ Action::make('send-payment-mail') - ->label('Envoyer le mail de paiement') + ->label(__('members.actions.send_payment_mail')) ->icon('heroicon-o-envelope') ->action(function () { // Mail de paiement pour nouvelle inscription (Job) }), Action::make('send-renewal-mail') - ->label('Envoyer un mail de relance') + ->label(__('members.actions.send_renewal_mail')) ->icon('heroicon-o-envelope') ->action(function () { // Mail de relance à créer (Job) diff --git a/app/Filament/Resources/Memberships/Schemas/MembershipForm.php b/app/Filament/Resources/Memberships/Schemas/MembershipForm.php index 1a83139..c59f36e 100644 --- a/app/Filament/Resources/Memberships/Schemas/MembershipForm.php +++ b/app/Filament/Resources/Memberships/Schemas/MembershipForm.php @@ -40,14 +40,14 @@ class MembershipForm | TAB : Informations générales |-------------------------------------------------------------------------- */ - Tabs\Tab::make('Informations générales') + Tabs\Tab::make(__('memberships.tabs.general_info')) ->icon(Heroicon::OutlinedInformationCircle) ->schema([ - Section::make('Adhérent') + Section::make(__('memberships.sections.member')) ->headerActions([ Action::make('view-profile') ->icon('heroicon-o-user') - ->label('Voir le profil du membre') + ->label(__('memberships.actions.view_profile')) ->action(function (Membership $record) { return redirect()->route('filament.admin.resources.members.edit', ['record' => $record->member_id]); }), @@ -62,7 +62,7 @@ class MembershipForm ]) ->columns(2), - Section::make('Informations de transaction') + Section::make(__('memberships.sections.transaction')) ->schema([ Select::make('package_id') ->label(Membership::getAttributeLabel('package_id')) @@ -89,136 +89,131 @@ class MembershipForm | TAB : Services/Modules |-------------------------------------------------------------------------- */ - Tabs\Tab::make('Modules') + Tabs\Tab::make(__('memberships.tabs.modules')) ->icon(Heroicon::OutlinedPuzzlePiece) ->schema([ - /* - | Messageries ISPConfig (lecture seule) - */ - Section::make('Messagerie ISPConfig') + Section::make(__('memberships.sections.ispconfig_mail')) ->afterHeader([ ServiceToggleAction::forService('mail'), ]) ->collapsible() ->schema([ RepeatableEntry::make('ispconfig_mails') - ->label('Données ISPConfig Mail') - ->state(fn(?Membership $record) => $record?->member?->ispconfigs() + ->label(__('members.ispconfig.mail_data')) + ->state(fn (?Membership $record) => $record?->member?->ispconfigs() ->where('type', IspconfigType::MAIL) ->get() ) ->schema([ TextEntry::make('email') - ->label('Adresse email'), + ->label(__('members.ispconfig.email')), TextEntry::make('ispconfig_service_user_id') - ->label('ID ISPConfig'), + ->label(__('members.ispconfig.id')), TextEntry::make('data.mailuser.quota') - ->label('Quota'), + ->label(__('members.ispconfig.quota')), TextEntry::make('data.mailuser.domain') - ->label('Domaine') + ->label(__('members.ispconfig.domain')) ->default('retzien.fr'), ViewEntry::make('data') ->label('JSON') ->view('filament.components.json-viewer') - ->viewData(fn($state) => [ + ->viewData(fn ($state) => [ 'data' => $state, ]) ->columnSpanFull(), ]) ->columns(2), ]) - ->visible(fn(?Membership $record) => $record?->member?->ispconfigs() + ->visible(fn (?Membership $record) => $record?->member?->ispconfigs() ->where('type', IspconfigType::MAIL) ->exists() ?? false ), - /* - | Hébergements web ISPConfig - */ - Section::make('Hébergements Web') + Section::make(__('memberships.sections.ispconfig_web')) ->afterHeader([ ServiceToggleAction::forService('webhosting'), ]) ->collapsible() ->schema([ RepeatableEntry::make('ispconfigs_web') - ->label('Données ISPConfig Web') - ->state(fn(?Membership $record) => $record?->member?->ispconfigs() + ->label(__('members.ispconfig.web_data')) + ->state(fn (?Membership $record) => $record?->member?->ispconfigs() ->where('type', IspconfigType::WEB) ->get() - ->map(fn($ispconfig) => $ispconfig->toArray()) + ->map(fn ($ispconfig) => $ispconfig->toArray()) ->all() ) ->schema([ TextEntry::make('data.domain_id') - ->label('ID ISPConfig'), + ->label(__('members.ispconfig.id')), TextEntry::make('data.domain') - ->label('Domaine'), + ->label(__('members.ispconfig.domain')), TextEntry::make('data.active') - ->label('État') - ->formatStateUsing(fn($state) => $state === 'y' ? 'Activé' : 'Désactivé' + ->label(__('members.ispconfig.state')) + ->formatStateUsing(fn ($state) => $state === 'y' + ? __('members.ispconfig.enabled') + : __('members.ispconfig.disabled') ), ViewEntry::make('data') ->label('JSON') ->view('filament.components.json-viewer') - ->viewData(fn($state) => [ + ->viewData(fn ($state) => [ 'data' => $state, ]) ->columnSpanFull(), ]) ->columns(3), ]) - ->visible(fn(?Membership $record) => $record?->member?->ispconfigs() + ->visible(fn (?Membership $record) => $record?->member?->ispconfigs() ->where('type', IspconfigType::WEB) ->exists() ?? false ), - /* - | Compte(s) NextCloud (lecture seule) - */ - Section::make('NextCloud') + Section::make(__('memberships.sections.nextcloud')) ->afterHeader([ ServiceToggleAction::forService('nextcloud'), ]) ->collapsible() ->schema([ RepeatableEntry::make('nextcloud_accounts') - ->label('Données NextCloud') - ->state(fn(?Membership $record) => $record?->member?->nextcloudAccounts() + ->label(__('members.ispconfig.nextcloud_data')) + ->state(fn (?Membership $record) => $record?->member?->nextcloudAccounts() ->get() - ->map(fn($nextcloudAccount) => $nextcloudAccount->toArray()) + ->map(fn ($nextcloudAccount) => $nextcloudAccount->toArray()) ->all() ) ->schema([ TextEntry::make('nextcloud_user_id') - ->label('Id Nextcloud'), + ->label(__('members.ispconfig.nextcloud_id')), TextEntry::make('data.displayname') - ->label('Nom de l\'utilisateur'), + ->label(__('members.ispconfig.display_name')), TextEntry::make('data.enabled') - ->label('État') - ->formatStateUsing(fn($state) => $state == 'true' ? 'Activé' : 'Désactivé' + ->label(__('members.ispconfig.state')) + ->formatStateUsing(fn ($state) => $state == 'true' + ? __('members.ispconfig.enabled') + : __('members.ispconfig.disabled') ), ViewEntry::make('data') ->label('JSON') ->view('filament.components.json-viewer') - ->viewData(fn($state) => [ + ->viewData(fn ($state) => [ 'data' => $state, ]) ->columnSpanFull(), ]) ->columns(3), ]) - ->visible(fn(?Membership $record) => $record?->member?->nextcloudAccounts() + ->visible(fn (?Membership $record) => $record?->member?->nextcloudAccounts() ->exists() ?? false ), ]), @@ -234,7 +229,7 @@ class MembershipForm */ Grid::make(1) ->schema([ - Section::make('Statut') + Section::make(__('memberships.sections.status')) ->schema([ Select::make('status') ->label(Membership::getAttributeLabel('status')) diff --git a/app/Filament/Resources/Memberships/Tables/MembershipsTable.php b/app/Filament/Resources/Memberships/Tables/MembershipsTable.php index 284fb46..7955313 100644 --- a/app/Filament/Resources/Memberships/Tables/MembershipsTable.php +++ b/app/Filament/Resources/Memberships/Tables/MembershipsTable.php @@ -11,7 +11,6 @@ use Filament\Tables\Enums\FiltersLayout; use Filament\Tables\Filters\QueryBuilder; use Filament\Tables\Filters\QueryBuilder\Constraints\DateConstraint; use Filament\Tables\Filters\QueryBuilder\Constraints\SelectConstraint; -use Filament\Tables\Filters\SelectFilter; use Filament\Tables\Table; class MembershipsTable @@ -84,29 +83,27 @@ class MembershipsTable 'amount', ]) ->filters([ - // Filtres pour status, date de début et date de fin, status de paiement - QueryBuilder::make() ->constraints([ SelectConstraint::make('status') - ->label('Statut de l\'adhésion') + ->label(Membership::getAttributeLabel('status')) ->options([ - 'active' => 'Active', - 'expired' => 'Expirée', - 'pending' => 'En attente', + 'active' => Membership::getAttributeLabel('active'), + 'expired' => Membership::getAttributeLabel('expired'), + 'pending' => Membership::getAttributeLabel('pending'), ]), DateConstraint::make('start_date') - ->label('Date de début'), + ->label(Membership::getAttributeLabel('start_date')), DateConstraint::make('end_date') - ->label('Date de fin'), + ->label(Membership::getAttributeLabel('end_date')), SelectConstraint::make('payment_status') - ->label('Statut de paiement') + ->label(Membership::getAttributeLabel('payment_status')) ->options([ - 'paid' => 'Payée', - 'unpaid' => 'Impayée', - 'partial' => 'Partiellement payée' + 'paid' => Membership::getAttributeLabel('paid'), + 'unpaid' => Membership::getAttributeLabel('unpaid'), + 'partial' => Membership::getAttributeLabel('partial'), ]), - ]), + ]), ], layout: FiltersLayout::Modal) ->recordActions([ EditAction::make(), diff --git a/app/Filament/Resources/NotificationTemplates/Schemas/NotificationTemplateForm.php b/app/Filament/Resources/NotificationTemplates/Schemas/NotificationTemplateForm.php index 303e272..c16c861 100644 --- a/app/Filament/Resources/NotificationTemplates/Schemas/NotificationTemplateForm.php +++ b/app/Filament/Resources/NotificationTemplates/Schemas/NotificationTemplateForm.php @@ -32,11 +32,15 @@ class NotificationTemplateForm TextInput::make('subject') ->label(NotificationTemplate::getAttributeLabel('subject')) ->required() - ->helperText('Variables : {member_name}, {expiry_date}'), + ->helperText(fn (?NotificationTemplate $record) => $record?->variables + ? NotificationTemplate::getAttributeLabel('variables').' : '.implode(', ', array_map(fn ($k) => '{'.$k.'}', array_keys($record->variables))) + : null), RichEditor::make('body') ->label(NotificationTemplate::getAttributeLabel('body')) ->required() - ->helperText('Variables : {member_name}, {expiry_date}') + ->helperText(fn (?NotificationTemplate $record) => $record?->variables + ? NotificationTemplate::getAttributeLabel('variables').' : '.implode(', ', array_map(fn ($k) => '{'.$k.'}', array_keys($record->variables))) + : null) ->columnSpanFull(), ]), ]); diff --git a/app/Filament/Resources/Packages/PackageResource.php b/app/Filament/Resources/Packages/PackageResource.php index b4419b1..d2c4a7a 100644 --- a/app/Filament/Resources/Packages/PackageResource.php +++ b/app/Filament/Resources/Packages/PackageResource.php @@ -7,14 +7,12 @@ use App\Filament\Resources\Packages\Pages\EditPackage; use App\Filament\Resources\Packages\Pages\ListPackages; use App\Filament\Resources\Packages\Schemas\PackageForm; use App\Filament\Resources\Packages\Tables\PackagesTable; +use App\Models\Package; use BackedEnum; use Filament\Resources\Resource; use Filament\Schemas\Schema; use Filament\Support\Icons\Heroicon; use Filament\Tables\Table; -use Illuminate\Database\Eloquent\Builder; -use Illuminate\Database\Eloquent\SoftDeletingScope; -use App\Models\Package; class PackageResource extends Resource { @@ -24,6 +22,16 @@ class PackageResource extends Resource protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedShoppingCart; + public static function getModelLabel(): string + { + return Package::getAttributeLabel('package'); + } + + public static function getPluralModelLabel(): string + { + return Package::getAttributeLabel('packages'); + } + public static function form(Schema $schema): Schema { return PackageForm::configure($schema); diff --git a/app/Filament/Resources/Services/ServiceResource.php b/app/Filament/Resources/Services/ServiceResource.php index 4f79fee..3a30199 100644 --- a/app/Filament/Resources/Services/ServiceResource.php +++ b/app/Filament/Resources/Services/ServiceResource.php @@ -22,6 +22,16 @@ class ServiceResource extends Resource protected static string|BackedEnum|null $navigationIcon = Heroicon::OutlinedPuzzlePiece; + public static function getModelLabel(): string + { + return Service::getAttributeLabel('service'); + } + + public static function getPluralModelLabel(): string + { + return Service::getAttributeLabel('services'); + } + public static function form(Schema $schema): Schema { return ServiceForm::configure($schema); diff --git a/app/Filament/Resources/Services/Tables/ServicesTable.php b/app/Filament/Resources/Services/Tables/ServicesTable.php index 4f06866..2281b45 100644 --- a/app/Filament/Resources/Services/Tables/ServicesTable.php +++ b/app/Filament/Resources/Services/Tables/ServicesTable.php @@ -6,7 +6,6 @@ use App\Models\Service; use Filament\Actions\BulkActionGroup; use Filament\Actions\DeleteBulkAction; use Filament\Actions\EditAction; -use Filament\Support\Icons\Heroicon; use Filament\Tables\Columns\IconColumn; use Filament\Tables\Columns\TextColumn; use Filament\Tables\Table; @@ -24,8 +23,8 @@ class ServicesTable ->label(Service::getAttributeLabel('identifier')) ->searchable(), IconColumn::make('icon') - ->label('Icône') - ->icon(fn (Service $record) => 'heroicon-o-' . $record->icon), + ->label(Service::getAttributeLabel('icon')) + ->icon(fn (Service $record) => 'heroicon-o-'.$record->icon), TextColumn::make('created_at') ->dateTime() ->sortable() diff --git a/app/Filament/Resources/Users/Pages/CreateUser.php b/app/Filament/Resources/Users/Pages/CreateUser.php index 125b3ff..0edc121 100644 --- a/app/Filament/Resources/Users/Pages/CreateUser.php +++ b/app/Filament/Resources/Users/Pages/CreateUser.php @@ -3,9 +3,44 @@ namespace App\Filament\Resources\Users\Pages; use App\Filament\Resources\Users\UserResource; +use App\Models\User; +use App\Notifications\AdminInvitationNotification; use Filament\Resources\Pages\CreateRecord; +use Illuminate\Support\Facades\Log; +use Illuminate\Support\Facades\Password; +use Illuminate\Support\Str; class CreateUser extends CreateRecord { protected static string $resource = UserResource::class; + + /** + * Generate a random password if none was provided, so the invitation + * flow can proceed without requiring the admin to set one manually. + * + * @param array $data + * @return array + */ + protected function mutateFormDataBeforeCreate(array $data): array + { + if (empty($data['password'])) { + $data['password'] = Str::random(32); + } + + return $data; + } + + /** + * Send an invitation email after the user is created so they can + * set their own password via the admin panel reset flow. + */ + protected function afterCreate(): void + { + /** @var User $user */ + $user = $this->record; + $token = Password::broker()->createToken($user); + + $user->notify(new AdminInvitationNotification($token)); + Log::info('User invited: '.$user->email); + } } diff --git a/app/Filament/Resources/Users/Schemas/UserForm.php b/app/Filament/Resources/Users/Schemas/UserForm.php index 88e151a..77093fe 100644 --- a/app/Filament/Resources/Users/Schemas/UserForm.php +++ b/app/Filament/Resources/Users/Schemas/UserForm.php @@ -20,7 +20,6 @@ class UserForm ->required(), TextInput::make('email') ->label(User::getAttributeLabel('email')) - ->label('Email address') ->email() ->required(), DateTimePicker::make('email_verified_at') @@ -28,14 +27,19 @@ class UserForm TextInput::make('password') ->label(User::getAttributeLabel('password')) ->password() + ->revealable() ->dehydrated(fn ($state) => filled($state)) - ->dehydrateStateUsing(fn ($state) => Hash::make($state)), + ->dehydrateStateUsing(fn ($state) => Hash::make($state)) + ->hint(fn (string $operation) => $operation === 'create' + ? __('users.hints.password_create') + : __('users.hints.password_edit')) + ->hintIcon('heroicon-m-information-circle'), Select::make('role') ->label(User::getAttributeLabel('role')) ->relationship('roles', 'name') ->multiple() ->preload() - ->searchable() + ->searchable(), ]); } } diff --git a/app/Filament/Resources/Users/Tables/UsersTable.php b/app/Filament/Resources/Users/Tables/UsersTable.php index e0da462..b0a48cc 100644 --- a/app/Filament/Resources/Users/Tables/UsersTable.php +++ b/app/Filament/Resources/Users/Tables/UsersTable.php @@ -2,6 +2,7 @@ namespace App\Filament\Resources\Users\Tables; +use App\Models\User; use Filament\Actions\BulkActionGroup; use Filament\Actions\DeleteBulkAction; use Filament\Actions\EditAction; @@ -15,11 +16,13 @@ class UsersTable return $table ->columns([ TextColumn::make('name') + ->label(User::getAttributeLabel('name')) ->searchable(), TextColumn::make('email') - ->label('Email address') + ->label(User::getAttributeLabel('email')) ->searchable(), TextColumn::make('email_verified_at') + ->label(User::getAttributeLabel('email_verified_at')) ->dateTime() ->sortable(), TextColumn::make('created_at') diff --git a/app/Models/User.php b/app/Models/User.php index 582c73a..a790223 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -3,14 +3,15 @@ namespace App\Models; // use Illuminate\Contracts\Auth\MustVerifyEmail; +use App\Notifications\AdminPasswordResetNotification; +use Filament\Models\Contracts\FilamentUser; +use Filament\Panel; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Laravel\Fortify\TwoFactorAuthenticatable; use Spatie\Permission\Traits\HasRoles; -use Filament\Models\Contracts\FilamentUser; -use Filament\Panel; /** * @property int $id @@ -29,6 +30,7 @@ use Filament\Panel; * @property-read int|null $permissions_count * @property-read \Illuminate\Database\Eloquent\Collection $roles * @property-read int|null $roles_count + * * @method static \Database\Factories\UserFactory factory($count = null, $state = []) * @method static \Illuminate\Database\Eloquent\Builder|User newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|User newQuery() @@ -45,11 +47,12 @@ use Filament\Panel; * @method static \Illuminate\Database\Eloquent\Builder|User whereUpdatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|User withoutPermission($permissions) * @method static \Illuminate\Database\Eloquent\Builder|User withoutRole($roles, $guard = null) + * * @mixin \Eloquent */ class User extends Authenticatable implements FilamentUser { - use HasRoles, HasFactory, Notifiable, TwoFactorAuthenticatable; + use HasFactory, HasRoles, Notifiable, TwoFactorAuthenticatable; /** * The attributes that are mass assignable. @@ -87,12 +90,11 @@ class User extends Authenticatable implements FilamentUser public function canAccessPanel(Panel $panel): bool { - //return str_ends_with($this->email, '@yourdomain.com') && $this->hasVerifiedEmail(); - //@todo : restreindre aux adresses retzien.fr pour la prod + // return str_ends_with($this->email, '@yourdomain.com') && $this->hasVerifiedEmail(); + // @todo : restreindre aux adresses retzien.fr pour la prod return true; } - public static function getAttributeLabel(string $attribute): string { return __("users.fields.$attribute"); @@ -103,6 +105,10 @@ class User extends Authenticatable implements FilamentUser return __("roles.fields.' . $role"); }*/ + public function sendPasswordResetNotification($token): void + { + $this->notify(new AdminPasswordResetNotification($token)); + } public function members(): hasMany { diff --git a/app/Notifications/AdminInvitationNotification.php b/app/Notifications/AdminInvitationNotification.php new file mode 100644 index 0000000..ba4d7d8 --- /dev/null +++ b/app/Notifications/AdminInvitationNotification.php @@ -0,0 +1,44 @@ + + */ + public function via(object $notifiable): array + { + return ['mail']; + } + + public function toMail(object $notifiable): MailMessage + { + $template = NotificationTemplate::findByIdentifier('admin_invitation'); + $url = Filament::getPanel('admin')->getResetPasswordUrl($this->token, $notifiable); + + $vars = [ + 'name' => $notifiable->name, + 'url' => $url, + 'app_name' => config('app.name'), + 'expire_minutes' => config('auth.passwords.users.expire'), + ]; + + return (new MailMessage) + ->subject($template->renderSubject($vars)) + ->view('notifications.mail-template', [ + 'body' => $template->renderBody($vars), + ]); + } +} diff --git a/app/Notifications/AdminPasswordResetNotification.php b/app/Notifications/AdminPasswordResetNotification.php new file mode 100644 index 0000000..459077b --- /dev/null +++ b/app/Notifications/AdminPasswordResetNotification.php @@ -0,0 +1,44 @@ + + */ + public function via(object $notifiable): array + { + return ['mail']; + } + + public function toMail(object $notifiable): MailMessage + { + $template = NotificationTemplate::findByIdentifier('admin_password_reset'); + $url = Filament::getPanel('admin')->getResetPasswordUrl($this->token, $notifiable); + + $vars = [ + 'name' => $notifiable->name, + 'url' => $url, + 'app_name' => config('app.name'), + 'expire_minutes' => config('auth.passwords.users.expire'), + ]; + + return (new MailMessage) + ->subject($template->renderSubject($vars)) + ->view('notifications.mail-template', [ + 'body' => $template->renderBody($vars), + ]); + } +} diff --git a/app/Providers/Filament/AdminPanelProvider.php b/app/Providers/Filament/AdminPanelProvider.php index 838b9bb..94c738b 100644 --- a/app/Providers/Filament/AdminPanelProvider.php +++ b/app/Providers/Filament/AdminPanelProvider.php @@ -3,9 +3,9 @@ namespace App\Providers\Filament; use Andreia\FilamentNordTheme\FilamentNordThemePlugin; -use BezhanSalleh\FilamentShield\FilamentShieldPlugin; use App\Filament\Resources\Members\Widgets\MemberCount; use App\Filament\Resources\Memberships\Widgets\MembershipsChart; +use BezhanSalleh\FilamentShield\FilamentShieldPlugin; use Filament\Http\Middleware\Authenticate; use Filament\Http\Middleware\AuthenticateSession; use Filament\Http\Middleware\DisableBladeIconComponents; @@ -33,6 +33,8 @@ class AdminPanelProvider extends PanelProvider ->id('admin') ->path('admin') ->login() + ->passwordReset() + ->profile(isSimple: false) ->colors([ 'primary' => Color::Rose, ]) @@ -46,7 +48,7 @@ class AdminPanelProvider extends PanelProvider AccountWidget::class, FilamentInfoWidget::class, MemberCount::class, - //MembershipsChart::class, + // MembershipsChart::class, ]) ->middleware([ EncryptCookies::class, diff --git a/app/Services/ListMonk/ListMonkService.php b/app/Services/ListMonk/ListMonkService.php new file mode 100644 index 0000000..03ac25f --- /dev/null +++ b/app/Services/ListMonk/ListMonkService.php @@ -0,0 +1,222 @@ +http = Http::withBasicAuth( + config('services.listmonk.username'), + config('services.listmonk.password') + ) + ->withHeaders(['Accept' => 'application/json']) + ->baseUrl(config('services.listmonk.base_url').'/api'); + } + + // ------------------------------------------------------------------------- + // Lists + // ------------------------------------------------------------------------- + + /** + * Retrieve all mailing lists. + * + * @throws ConnectionException + */ + public function getLists(): array + { + return $this->http + ->get('/lists', ['per_page' => 'all']) + ->json('data.results') ?? []; + } + + /** + * Retrieve a single list by its ID. + * + * @throws ConnectionException + */ + public function getList(int $listId): ?array + { + $response = $this->http->get("/lists/{$listId}"); + + if (! $response->successful()) { + return null; + } + + return $response->json('data'); + } + + // ------------------------------------------------------------------------- + // Subscribers + // ------------------------------------------------------------------------- + + /** + * Retrieve subscribers with optional pagination. + * + * @throws ConnectionException + */ + public function getSubscribers(int $page = 1, int $perPage = 100): array + { + return $this->http + ->get('/subscribers', [ + 'page' => $page, + 'per_page' => $perPage, + ]) + ->json('data.results') ?? []; + } + + /** + * Retrieve a single subscriber by their Listmonk ID. + * + * @throws ConnectionException + */ + public function getSubscriber(int $subscriberId): ?array + { + $response = $this->http->get("/subscribers/{$subscriberId}"); + + if (! $response->successful()) { + return null; + } + + return $response->json('data'); + } + + /** + * Find a subscriber by their email address. + * + * @throws ConnectionException + */ + public function getSubscriberByEmail(string $email): ?array + { + $results = $this->http + ->get('/subscribers', ['query' => "subscribers.email = '{$email}'"]) + ->json('data.results') ?? []; + + return $results[0] ?? null; + } + + /** + * Create a new subscriber and enrol them in the given lists. + * + * @param array $listIds IDs of the lists to subscribe to. + * @param array $attribs Custom attributes (e.g. language preference). + * + * @throws ConnectionException + */ + public function createSubscriber( + string $email, + string $name, + array $listIds = [], + array $attribs = [], + string $status = 'enabled', + ): ?array { + $response = $this->http->post('/subscribers', [ + 'email' => $email, + 'name' => $name, + 'status' => $status, + 'lists' => $listIds, + 'attribs' => $attribs, + ]); + + if (! $response->successful()) { + return null; + } + + return $response->json('data'); + } + + /** + * Update an existing subscriber's information. + * + * @param array $listIds + * @param array $attribs + * + * @throws ConnectionException + */ + public function updateSubscriber( + int $subscriberId, + string $email, + string $name, + array $listIds = [], + array $attribs = [], + string $status = 'enabled', + ): bool { + $response = $this->http->put("/subscribers/{$subscriberId}", [ + 'email' => $email, + 'name' => $name, + 'status' => $status, + 'lists' => $listIds, + 'attribs' => $attribs, + ]); + + return $response->successful(); + } + + /** + * Subscribe or unsubscribe a set of subscribers from lists. + * + * @param array $subscriberIds + * @param array $listIds + * @param string $action subscribe | unsubscribe + * @param string $status confirmed | unconfirmed + * + * @throws ConnectionException + */ + public function updateSubscriberLists( + array $subscriberIds, + array $listIds, + string $action = 'subscribe', + string $status = 'confirmed', + ): bool { + $response = $this->http->put('/subscribers/lists', [ + 'ids' => $subscriberIds, + 'action' => $action, + 'status' => $status, + 'list_ids' => $listIds, + ]); + + return $response->successful(); + } + + /** + * Add a subscriber to the blocklist. + * + * @throws ConnectionException + */ + public function blocklistSubscriber(int $subscriberId): bool + { + return $this->http + ->put("/subscribers/{$subscriberId}/blocklist") + ->successful(); + } + + /** + * Permanently delete a subscriber. + * + * @throws ConnectionException + */ + public function deleteSubscriber(int $subscriberId): bool + { + return $this->http + ->delete("/subscribers/{$subscriberId}") + ->successful(); + } + + /** + * Send an opt-in confirmation email to a subscriber. + * + * @throws ConnectionException + */ + public function sendOptin(int $subscriberId): bool + { + return $this->http + ->post("/subscribers/{$subscriberId}/optin") + ->successful(); + } +} diff --git a/config/fortify.php b/config/fortify.php index df49e4f..51986a2 100644 --- a/config/fortify.php +++ b/config/fortify.php @@ -145,7 +145,7 @@ return [ 'features' => [ // Features::registration(), - // Features::resetPasswords(), + Features::resetPasswords(), // Features::emailVerification(), // Features::updateProfileInformation(), // Features::updatePasswords(), diff --git a/config/logging.php b/config/logging.php index 1a7ddb2..e99403c 100644 --- a/config/logging.php +++ b/config/logging.php @@ -18,7 +18,7 @@ return [ | */ - 'default' => env('LOG_CHANNEL', 'daily'), + 'default' => env('LOG_CHANNEL', 'stack'), /* |-------------------------------------------------------------------------- @@ -63,6 +63,7 @@ return [ 'path' => storage_path('logs/laravel.log'), 'level' => env('LOG_LEVEL', 'debug'), 'replace_placeholders' => true, + 'days' => 30, ], 'daily' => [ diff --git a/config/services.php b/config/services.php index 1b770ca..efef054 100644 --- a/config/services.php +++ b/config/services.php @@ -40,7 +40,7 @@ return [ 'htaccess_url' => env('DOLIBARR_URL_HTACCESS'), 'username' => env('DOLIBARR_USERNAME'), 'password' => env('DOLIBARR_PWD'), - 'api_key' => env('DOLIBARR_APIKEY') + 'api_key' => env('DOLIBARR_APIKEY'), ], 'ispconfig' => [ 'servers' => [ @@ -71,6 +71,11 @@ return [ 'nextcloud' => [ 'url' => env('NEXTCLOUD_URL'), 'user' => env('NEXTCLOUD_USERNAME'), - 'password' => env('NEXTCLOUD_PASSWORD') + 'password' => env('NEXTCLOUD_PASSWORD'), + ], + 'listmonk' => [ + 'base_url' => env('LISTMONK_URL'), + 'username' => env('LISTMONK_USERNAME'), + 'password' => env('LISTMONK_PASSWORD'), ], ]; diff --git a/database/seeders/NotificationTemplateSeeder.php b/database/seeders/NotificationTemplateSeeder.php index 749420c..c486853 100644 --- a/database/seeders/NotificationTemplateSeeder.php +++ b/database/seeders/NotificationTemplateSeeder.php @@ -25,5 +25,46 @@ class NotificationTemplateSeeder extends Seeder 'is_active' => true, ] ); + + NotificationTemplate::updateOrCreate( + ['identifier' => 'admin_invitation'], + [ + 'name' => 'Invitation administrateur', + 'subject' => 'Bienvenue sur {app_name} — Configurez votre mot de passe', + 'body' => '

Bonjour {name},

' + .'

Un administrateur a créé un compte pour vous sur {app_name}.

' + .'

Cliquez sur le lien ci-dessous pour configurer votre mot de passe et accéder au back office.

' + .'

Configurer mon mot de passe

' + .'

Ce lien expire dans {expire_minutes} minutes.

' + .'

Si vous n\'attendiez pas cette invitation, vous pouvez ignorer cet e-mail en toute sécurité.

', + 'variables' => [ + 'name' => 'Nom de l\'utilisateur', + 'url' => 'URL de configuration du mot de passe', + 'app_name' => 'Nom de l\'application', + 'expire_minutes' => 'Durée de validité du lien (minutes)', + ], + 'is_active' => true, + ] + ); + + NotificationTemplate::updateOrCreate( + ['identifier' => 'admin_password_reset'], + [ + 'name' => 'Réinitialisation de mot de passe', + 'subject' => 'Réinitialiser votre mot de passe — {app_name}', + 'body' => '

Bonjour {name},

' + .'

Vous recevez cet e-mail car une demande de réinitialisation de mot de passe a été effectuée pour votre compte.

' + .'

Réinitialiser mon mot de passe

' + .'

Ce lien expire dans {expire_minutes} minutes.

' + .'

Si vous n\'avez pas demandé de réinitialisation de mot de passe, aucune action supplémentaire n\'est requise.

', + 'variables' => [ + 'name' => 'Nom de l\'utilisateur', + 'url' => 'URL de réinitialisation du mot de passe', + 'app_name' => 'Nom de l\'application', + 'expire_minutes' => 'Durée de validité du lien (minutes)', + ], + 'is_active' => true, + ] + ); } } diff --git a/lang/en/members.php b/lang/en/members.php index 99cc5d2..75cfafe 100644 --- a/lang/en/members.php +++ b/lang/en/members.php @@ -23,4 +23,40 @@ return [ 'updated_at' => 'Updated at', 'deleted_at' => 'Deleted at', ], + + 'tabs' => [ + 'general_info' => 'General information', + 'modules' => 'Modules', + ], + + 'sections' => [ + 'personal_info' => 'Personal information', + 'administrative_info' => 'Administrative information', + 'contact_info' => 'Contact details', + 'status' => 'Status', + 'actions' => 'Actions', + 'ispconfig_mail' => 'ISPConfig Mail', + 'ispconfig_web' => 'Web Hosting', + 'nextcloud' => 'NextCloud', + ], + + 'ispconfig' => [ + 'mail_data' => 'ISPConfig Mail data', + 'web_data' => 'ISPConfig Web data', + 'nextcloud_data' => 'NextCloud data', + 'email' => 'Email address', + 'id' => 'ISPConfig ID', + 'quota' => 'Quota', + 'domain' => 'Domain', + 'state' => 'State', + 'enabled' => 'Enabled', + 'disabled' => 'Disabled', + 'nextcloud_id' => 'Nextcloud ID', + 'display_name' => 'Display name', + ], + + 'actions' => [ + 'send_payment_mail' => 'Send payment email', + 'send_renewal_mail' => 'Send follow-up email', + ], ]; diff --git a/lang/en/membership.php b/lang/en/membership.php deleted file mode 100644 index a1b2cba..0000000 --- a/lang/en/membership.php +++ /dev/null @@ -1,27 +0,0 @@ - 'Membership', - 'memberships' => 'Memberships', - 'member' => 'Member', - 'members' => 'Members', - 'member_id' => 'Member ID', - 'admin_id' => 'Created by', - 'status' => 'Status', - 'pending' => 'Pending', - 'active' => 'Active', - 'expired' => 'Expired', - 'start_date' => 'Start date', - 'end_date' => 'End date', - 'amount' => 'Amount', - 'payment_status' => 'Payment status', - 'paid' => 'Paid', - 'unpaid' => 'Unpaid', - 'partial' => 'Partially paid', - 'created_at' => 'Created at', - 'updated_at' => 'Updated at', - 'subscription' => [ - 'success' => 'Your subscription has been successfully sent. We will contact you as soon as possible.', - 'error' => 'An error occurred while sending your subscription.', - ] -]; diff --git a/lang/en/memberships.php b/lang/en/memberships.php new file mode 100644 index 0000000..38d7879 --- /dev/null +++ b/lang/en/memberships.php @@ -0,0 +1,52 @@ + [ + 'membership' => 'Membership', + 'memberships' => 'Memberships', + 'member' => 'Member', + 'members' => 'Members', + 'member_id' => 'Member', + 'select_member' => 'Select a member...', + 'select_author' => 'Select an author...', + 'admin_id' => 'Created by', + 'status' => 'Status', + 'pending' => 'Pending', + 'active' => 'Active', + 'expired' => 'Expired', + 'package_id' => 'Package', + 'select_package' => 'Select a package...', + 'start_date' => 'Start date', + 'end_date' => 'End date', + 'amount' => 'Amount', + 'payment_status' => 'Payment status', + 'paid' => 'Paid', + 'unpaid' => 'Unpaid', + 'partial' => 'Partially paid', + 'services' => 'Services', + 'created_at' => 'Created at', + 'updated_at' => 'Updated at', + 'subscription' => [ + 'success' => 'Your subscription has been successfully submitted. We will contact you as soon as possible.', + 'error' => 'An error occurred while submitting your subscription.', + ], + ], + + 'tabs' => [ + 'general_info' => 'General information', + 'modules' => 'Modules', + ], + + 'sections' => [ + 'member' => 'Member', + 'transaction' => 'Transaction details', + 'status' => 'Status', + 'ispconfig_mail' => 'ISPConfig Mail', + 'ispconfig_web' => 'Web Hosting', + 'nextcloud' => 'NextCloud', + ], + + 'actions' => [ + 'view_profile' => 'View member profile', + ], +]; diff --git a/lang/en/modal.php b/lang/en/modal.php new file mode 100644 index 0000000..612832b --- /dev/null +++ b/lang/en/modal.php @@ -0,0 +1,23 @@ + 'Are you sure you want to delete?', + + 'actions' => [ + + 'cancel' => [ + 'label' => 'Cancel', + ], + + 'confirm' => [ + 'label' => 'Confirm', + ], + + 'submit' => [ + 'label' => 'Submit', + ], + + ], + +]; diff --git a/lang/en/notification_templates.php b/lang/en/notification_templates.php new file mode 100644 index 0000000..131697c --- /dev/null +++ b/lang/en/notification_templates.php @@ -0,0 +1,14 @@ + [ + 'singular_name' => 'Email Template', + 'plural_name' => 'Email Templates', + 'identifier' => 'Identifier', + 'name' => 'Name', + 'subject' => 'Subject', + 'body' => 'Content', + 'variables' => 'Available variables', + 'is_active' => 'Active', + ], +]; diff --git a/lang/en/users.php b/lang/en/users.php index ac3f05e..36de135 100644 --- a/lang/en/users.php +++ b/lang/en/users.php @@ -8,6 +8,11 @@ return [ 'email' => 'Email', 'email_verified_at' => 'Email verified at', 'password' => 'Password', - 'role' => 'Role' - ] + 'role' => 'Role', + ], + + 'hints' => [ + 'password_create' => 'Leave empty to send an invitation email.', + 'password_edit' => 'Leave empty to keep the current password.', + ], ]; diff --git a/lang/fr/members.php b/lang/fr/members.php index 8f7e140..e91e68a 100644 --- a/lang/fr/members.php +++ b/lang/fr/members.php @@ -36,7 +36,43 @@ return [ 'stats' => [ 'name' => 'Nouveaux Membres', 'description' => 'Nombre de nouveaux membres par an', - ] - ] + ], + ], + ], + + 'tabs' => [ + 'general_info' => 'Informations générales', + 'modules' => 'Modules', + ], + + 'sections' => [ + 'personal_info' => 'Informations personnelles', + 'administrative_info' => 'Informations administratives', + 'contact_info' => 'Coordonnées', + 'status' => 'Statut', + 'actions' => 'Actions', + 'ispconfig_mail' => 'Messagerie ISPConfig', + 'ispconfig_web' => 'Hébergements Web', + 'nextcloud' => 'NextCloud', + ], + + 'ispconfig' => [ + 'mail_data' => 'Données ISPConfig Mail', + 'web_data' => 'Données ISPConfig Web', + 'nextcloud_data' => 'Données NextCloud', + 'email' => 'Adresse email', + 'id' => 'ID ISPConfig', + 'quota' => 'Quota', + 'domain' => 'Domaine', + 'state' => 'État', + 'enabled' => 'Activé', + 'disabled' => 'Désactivé', + 'nextcloud_id' => 'Id Nextcloud', + 'display_name' => 'Nom de l\'utilisateur', + ], + + 'actions' => [ + 'send_payment_mail' => 'Envoyer le mail de paiement', + 'send_renewal_mail' => 'Envoyer un mail de relance', ], ]; diff --git a/lang/fr/memberships.php b/lang/fr/memberships.php index 884bd97..e2584d7 100644 --- a/lang/fr/memberships.php +++ b/lang/fr/memberships.php @@ -29,7 +29,24 @@ return [ 'subscription' => [ 'success' => 'Votre inscription a bien été envoyée. Nous vous recontacterons dans les plus brefs délais.', 'error' => 'Une erreur est survenue lors de l\'envoi de votre inscription.', - ] + ], ], + 'tabs' => [ + 'general_info' => 'Informations générales', + 'modules' => 'Modules', + ], + + 'sections' => [ + 'member' => 'Adhérent', + 'transaction' => 'Informations de transaction', + 'status' => 'Statut', + 'ispconfig_mail' => 'Messagerie ISPConfig', + 'ispconfig_web' => 'Hébergements Web', + 'nextcloud' => 'NextCloud', + ], + + 'actions' => [ + 'view_profile' => 'Voir le profil du membre', + ], ]; diff --git a/lang/fr/services.php b/lang/fr/services.php index 64155eb..200a552 100644 --- a/lang/fr/services.php +++ b/lang/fr/services.php @@ -8,6 +8,6 @@ return [ 'name' => 'Nom', 'description' => 'Description', 'url' => 'URL', - 'icon' => 'Icon', - ] + 'icon' => 'Icône', + ], ]; diff --git a/lang/fr/users.php b/lang/fr/users.php index 4ecd020..a26a9bd 100644 --- a/lang/fr/users.php +++ b/lang/fr/users.php @@ -8,6 +8,11 @@ return [ 'email' => 'Email', 'email_verified_at' => 'Email vérifié le', 'password' => 'Mot de passe (si changement)', - 'role' => 'Rôle' - ] + 'role' => 'Rôle', + ], + + 'hints' => [ + 'password_create' => 'Laisser vide pour envoyer un email d\'invitation.', + 'password_edit' => 'Laisser vide pour conserver le mot de passe actuel.', + ], ]; diff --git a/resources/js/actions/App/Http/Controllers/Auth/index.ts b/resources/js/actions/App/Http/Controllers/Auth/index.ts index b57bca5..863f64e 100644 --- a/resources/js/actions/App/Http/Controllers/Auth/index.ts +++ b/resources/js/actions/App/Http/Controllers/Auth/index.ts @@ -1,16 +1,16 @@ import AuthenticatedSessionController from './AuthenticatedSessionController' -import RegisteredUserController from './RegisteredUserController' import PasswordResetLinkController from './PasswordResetLinkController' import NewPasswordController from './NewPasswordController' +import RegisteredUserController from './RegisteredUserController' import EmailVerificationPromptController from './EmailVerificationPromptController' import VerifyEmailController from './VerifyEmailController' import EmailVerificationNotificationController from './EmailVerificationNotificationController' const Auth = { AuthenticatedSessionController: Object.assign(AuthenticatedSessionController, AuthenticatedSessionController), - RegisteredUserController: Object.assign(RegisteredUserController, RegisteredUserController), PasswordResetLinkController: Object.assign(PasswordResetLinkController, PasswordResetLinkController), NewPasswordController: Object.assign(NewPasswordController, NewPasswordController), + RegisteredUserController: Object.assign(RegisteredUserController, RegisteredUserController), EmailVerificationPromptController: Object.assign(EmailVerificationPromptController, EmailVerificationPromptController), VerifyEmailController: Object.assign(VerifyEmailController, VerifyEmailController), EmailVerificationNotificationController: Object.assign(EmailVerificationNotificationController, EmailVerificationNotificationController), diff --git a/resources/js/actions/Filament/Auth/Pages/EditProfile.ts b/resources/js/actions/Filament/Auth/Pages/EditProfile.ts new file mode 100644 index 0000000..af5281e --- /dev/null +++ b/resources/js/actions/Filament/Auth/Pages/EditProfile.ts @@ -0,0 +1,83 @@ +import { queryParams, type RouteQueryOptions, type RouteDefinition, type RouteFormDefinition } from './../../../../wayfinder' +/** +* @see \Filament\Auth\Pages\EditProfile::__invoke +* @see vendor/filament/filament/src/Auth/Pages/EditProfile.php:7 +* @route '/admin/profile' +*/ +const EditProfile = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: EditProfile.url(options), + method: 'get', +}) + +EditProfile.definition = { + methods: ["get","head"], + url: '/admin/profile', +} satisfies RouteDefinition<["get","head"]> + +/** +* @see \Filament\Auth\Pages\EditProfile::__invoke +* @see vendor/filament/filament/src/Auth/Pages/EditProfile.php:7 +* @route '/admin/profile' +*/ +EditProfile.url = (options?: RouteQueryOptions) => { + return EditProfile.definition.url + queryParams(options) +} + +/** +* @see \Filament\Auth\Pages\EditProfile::__invoke +* @see vendor/filament/filament/src/Auth/Pages/EditProfile.php:7 +* @route '/admin/profile' +*/ +EditProfile.get = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: EditProfile.url(options), + method: 'get', +}) + +/** +* @see \Filament\Auth\Pages\EditProfile::__invoke +* @see vendor/filament/filament/src/Auth/Pages/EditProfile.php:7 +* @route '/admin/profile' +*/ +EditProfile.head = (options?: RouteQueryOptions): RouteDefinition<'head'> => ({ + url: EditProfile.url(options), + method: 'head', +}) + +/** +* @see \Filament\Auth\Pages\EditProfile::__invoke +* @see vendor/filament/filament/src/Auth/Pages/EditProfile.php:7 +* @route '/admin/profile' +*/ +const EditProfileForm = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: EditProfile.url(options), + method: 'get', +}) + +/** +* @see \Filament\Auth\Pages\EditProfile::__invoke +* @see vendor/filament/filament/src/Auth/Pages/EditProfile.php:7 +* @route '/admin/profile' +*/ +EditProfileForm.get = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: EditProfile.url(options), + method: 'get', +}) + +/** +* @see \Filament\Auth\Pages\EditProfile::__invoke +* @see vendor/filament/filament/src/Auth/Pages/EditProfile.php:7 +* @route '/admin/profile' +*/ +EditProfileForm.head = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: EditProfile.url({ + [options?.mergeQuery ? 'mergeQuery' : 'query']: { + _method: 'HEAD', + ...(options?.query ?? options?.mergeQuery ?? {}), + } + }), + method: 'get', +}) + +EditProfile.form = EditProfileForm + +export default EditProfile \ No newline at end of file diff --git a/resources/js/actions/Filament/Auth/Pages/PasswordReset/RequestPasswordReset.ts b/resources/js/actions/Filament/Auth/Pages/PasswordReset/RequestPasswordReset.ts new file mode 100644 index 0000000..525d213 --- /dev/null +++ b/resources/js/actions/Filament/Auth/Pages/PasswordReset/RequestPasswordReset.ts @@ -0,0 +1,83 @@ +import { queryParams, type RouteQueryOptions, type RouteDefinition, type RouteFormDefinition } from './../../../../../wayfinder' +/** +* @see \Filament\Auth\Pages\PasswordReset\RequestPasswordReset::__invoke +* @see vendor/filament/filament/src/Auth/Pages/PasswordReset/RequestPasswordReset.php:7 +* @route '/admin/password-reset/request' +*/ +const RequestPasswordReset = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: RequestPasswordReset.url(options), + method: 'get', +}) + +RequestPasswordReset.definition = { + methods: ["get","head"], + url: '/admin/password-reset/request', +} satisfies RouteDefinition<["get","head"]> + +/** +* @see \Filament\Auth\Pages\PasswordReset\RequestPasswordReset::__invoke +* @see vendor/filament/filament/src/Auth/Pages/PasswordReset/RequestPasswordReset.php:7 +* @route '/admin/password-reset/request' +*/ +RequestPasswordReset.url = (options?: RouteQueryOptions) => { + return RequestPasswordReset.definition.url + queryParams(options) +} + +/** +* @see \Filament\Auth\Pages\PasswordReset\RequestPasswordReset::__invoke +* @see vendor/filament/filament/src/Auth/Pages/PasswordReset/RequestPasswordReset.php:7 +* @route '/admin/password-reset/request' +*/ +RequestPasswordReset.get = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: RequestPasswordReset.url(options), + method: 'get', +}) + +/** +* @see \Filament\Auth\Pages\PasswordReset\RequestPasswordReset::__invoke +* @see vendor/filament/filament/src/Auth/Pages/PasswordReset/RequestPasswordReset.php:7 +* @route '/admin/password-reset/request' +*/ +RequestPasswordReset.head = (options?: RouteQueryOptions): RouteDefinition<'head'> => ({ + url: RequestPasswordReset.url(options), + method: 'head', +}) + +/** +* @see \Filament\Auth\Pages\PasswordReset\RequestPasswordReset::__invoke +* @see vendor/filament/filament/src/Auth/Pages/PasswordReset/RequestPasswordReset.php:7 +* @route '/admin/password-reset/request' +*/ +const RequestPasswordResetForm = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: RequestPasswordReset.url(options), + method: 'get', +}) + +/** +* @see \Filament\Auth\Pages\PasswordReset\RequestPasswordReset::__invoke +* @see vendor/filament/filament/src/Auth/Pages/PasswordReset/RequestPasswordReset.php:7 +* @route '/admin/password-reset/request' +*/ +RequestPasswordResetForm.get = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: RequestPasswordReset.url(options), + method: 'get', +}) + +/** +* @see \Filament\Auth\Pages\PasswordReset\RequestPasswordReset::__invoke +* @see vendor/filament/filament/src/Auth/Pages/PasswordReset/RequestPasswordReset.php:7 +* @route '/admin/password-reset/request' +*/ +RequestPasswordResetForm.head = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: RequestPasswordReset.url({ + [options?.mergeQuery ? 'mergeQuery' : 'query']: { + _method: 'HEAD', + ...(options?.query ?? options?.mergeQuery ?? {}), + } + }), + method: 'get', +}) + +RequestPasswordReset.form = RequestPasswordResetForm + +export default RequestPasswordReset \ No newline at end of file diff --git a/resources/js/actions/Filament/Auth/Pages/PasswordReset/ResetPassword.ts b/resources/js/actions/Filament/Auth/Pages/PasswordReset/ResetPassword.ts new file mode 100644 index 0000000..4d92280 --- /dev/null +++ b/resources/js/actions/Filament/Auth/Pages/PasswordReset/ResetPassword.ts @@ -0,0 +1,83 @@ +import { queryParams, type RouteQueryOptions, type RouteDefinition, type RouteFormDefinition } from './../../../../../wayfinder' +/** +* @see \Filament\Auth\Pages\PasswordReset\ResetPassword::__invoke +* @see vendor/filament/filament/src/Auth/Pages/PasswordReset/ResetPassword.php:7 +* @route '/admin/password-reset/reset' +*/ +const ResetPassword = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: ResetPassword.url(options), + method: 'get', +}) + +ResetPassword.definition = { + methods: ["get","head"], + url: '/admin/password-reset/reset', +} satisfies RouteDefinition<["get","head"]> + +/** +* @see \Filament\Auth\Pages\PasswordReset\ResetPassword::__invoke +* @see vendor/filament/filament/src/Auth/Pages/PasswordReset/ResetPassword.php:7 +* @route '/admin/password-reset/reset' +*/ +ResetPassword.url = (options?: RouteQueryOptions) => { + return ResetPassword.definition.url + queryParams(options) +} + +/** +* @see \Filament\Auth\Pages\PasswordReset\ResetPassword::__invoke +* @see vendor/filament/filament/src/Auth/Pages/PasswordReset/ResetPassword.php:7 +* @route '/admin/password-reset/reset' +*/ +ResetPassword.get = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: ResetPassword.url(options), + method: 'get', +}) + +/** +* @see \Filament\Auth\Pages\PasswordReset\ResetPassword::__invoke +* @see vendor/filament/filament/src/Auth/Pages/PasswordReset/ResetPassword.php:7 +* @route '/admin/password-reset/reset' +*/ +ResetPassword.head = (options?: RouteQueryOptions): RouteDefinition<'head'> => ({ + url: ResetPassword.url(options), + method: 'head', +}) + +/** +* @see \Filament\Auth\Pages\PasswordReset\ResetPassword::__invoke +* @see vendor/filament/filament/src/Auth/Pages/PasswordReset/ResetPassword.php:7 +* @route '/admin/password-reset/reset' +*/ +const ResetPasswordForm = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: ResetPassword.url(options), + method: 'get', +}) + +/** +* @see \Filament\Auth\Pages\PasswordReset\ResetPassword::__invoke +* @see vendor/filament/filament/src/Auth/Pages/PasswordReset/ResetPassword.php:7 +* @route '/admin/password-reset/reset' +*/ +ResetPasswordForm.get = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: ResetPassword.url(options), + method: 'get', +}) + +/** +* @see \Filament\Auth\Pages\PasswordReset\ResetPassword::__invoke +* @see vendor/filament/filament/src/Auth/Pages/PasswordReset/ResetPassword.php:7 +* @route '/admin/password-reset/reset' +*/ +ResetPasswordForm.head = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: ResetPassword.url({ + [options?.mergeQuery ? 'mergeQuery' : 'query']: { + _method: 'HEAD', + ...(options?.query ?? options?.mergeQuery ?? {}), + } + }), + method: 'get', +}) + +ResetPassword.form = ResetPasswordForm + +export default ResetPassword \ No newline at end of file diff --git a/resources/js/actions/Filament/Auth/Pages/PasswordReset/index.ts b/resources/js/actions/Filament/Auth/Pages/PasswordReset/index.ts new file mode 100644 index 0000000..de72cc0 --- /dev/null +++ b/resources/js/actions/Filament/Auth/Pages/PasswordReset/index.ts @@ -0,0 +1,9 @@ +import RequestPasswordReset from './RequestPasswordReset' +import ResetPassword from './ResetPassword' + +const PasswordReset = { + RequestPasswordReset: Object.assign(RequestPasswordReset, RequestPasswordReset), + ResetPassword: Object.assign(ResetPassword, ResetPassword), +} + +export default PasswordReset \ No newline at end of file diff --git a/resources/js/actions/Filament/Auth/Pages/index.ts b/resources/js/actions/Filament/Auth/Pages/index.ts index cb45745..cbbc94a 100644 --- a/resources/js/actions/Filament/Auth/Pages/index.ts +++ b/resources/js/actions/Filament/Auth/Pages/index.ts @@ -1,7 +1,11 @@ import Login from './Login' +import PasswordReset from './PasswordReset' +import EditProfile from './EditProfile' const Pages = { Login: Object.assign(Login, Login), + PasswordReset: Object.assign(PasswordReset, PasswordReset), + EditProfile: Object.assign(EditProfile, EditProfile), } export default Pages \ No newline at end of file diff --git a/resources/js/routes/filament/admin/auth/index.ts b/resources/js/routes/filament/admin/auth/index.ts index 77b7ae2..482e9fc 100644 --- a/resources/js/routes/filament/admin/auth/index.ts +++ b/resources/js/routes/filament/admin/auth/index.ts @@ -1,4 +1,5 @@ import { queryParams, type RouteQueryOptions, type RouteDefinition, type RouteFormDefinition } from './../../../../wayfinder' +import passwordReset from './password-reset' /** * @see \Filament\Auth\Pages\Login::__invoke * @see vendor/filament/filament/src/Auth/Pages/Login.php:7 @@ -136,9 +137,92 @@ logoutForm.post = (options?: RouteQueryOptions): RouteFormDefinition<'post'> => logout.form = logoutForm +/** +* @see \Filament\Auth\Pages\EditProfile::__invoke +* @see vendor/filament/filament/src/Auth/Pages/EditProfile.php:7 +* @route '/admin/profile' +*/ +export const profile = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: profile.url(options), + method: 'get', +}) + +profile.definition = { + methods: ["get","head"], + url: '/admin/profile', +} satisfies RouteDefinition<["get","head"]> + +/** +* @see \Filament\Auth\Pages\EditProfile::__invoke +* @see vendor/filament/filament/src/Auth/Pages/EditProfile.php:7 +* @route '/admin/profile' +*/ +profile.url = (options?: RouteQueryOptions) => { + return profile.definition.url + queryParams(options) +} + +/** +* @see \Filament\Auth\Pages\EditProfile::__invoke +* @see vendor/filament/filament/src/Auth/Pages/EditProfile.php:7 +* @route '/admin/profile' +*/ +profile.get = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: profile.url(options), + method: 'get', +}) + +/** +* @see \Filament\Auth\Pages\EditProfile::__invoke +* @see vendor/filament/filament/src/Auth/Pages/EditProfile.php:7 +* @route '/admin/profile' +*/ +profile.head = (options?: RouteQueryOptions): RouteDefinition<'head'> => ({ + url: profile.url(options), + method: 'head', +}) + +/** +* @see \Filament\Auth\Pages\EditProfile::__invoke +* @see vendor/filament/filament/src/Auth/Pages/EditProfile.php:7 +* @route '/admin/profile' +*/ +const profileForm = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: profile.url(options), + method: 'get', +}) + +/** +* @see \Filament\Auth\Pages\EditProfile::__invoke +* @see vendor/filament/filament/src/Auth/Pages/EditProfile.php:7 +* @route '/admin/profile' +*/ +profileForm.get = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: profile.url(options), + method: 'get', +}) + +/** +* @see \Filament\Auth\Pages\EditProfile::__invoke +* @see vendor/filament/filament/src/Auth/Pages/EditProfile.php:7 +* @route '/admin/profile' +*/ +profileForm.head = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: profile.url({ + [options?.mergeQuery ? 'mergeQuery' : 'query']: { + _method: 'HEAD', + ...(options?.query ?? options?.mergeQuery ?? {}), + } + }), + method: 'get', +}) + +profile.form = profileForm + const auth = { login: Object.assign(login, login), + passwordReset: Object.assign(passwordReset, passwordReset), logout: Object.assign(logout, logout), + profile: Object.assign(profile, profile), } export default auth \ No newline at end of file diff --git a/resources/js/routes/filament/admin/auth/password-reset/index.ts b/resources/js/routes/filament/admin/auth/password-reset/index.ts new file mode 100644 index 0000000..d7a47fb --- /dev/null +++ b/resources/js/routes/filament/admin/auth/password-reset/index.ts @@ -0,0 +1,169 @@ +import { queryParams, type RouteQueryOptions, type RouteDefinition, type RouteFormDefinition } from './../../../../../wayfinder' +/** +* @see \Filament\Auth\Pages\PasswordReset\RequestPasswordReset::__invoke +* @see vendor/filament/filament/src/Auth/Pages/PasswordReset/RequestPasswordReset.php:7 +* @route '/admin/password-reset/request' +*/ +export const request = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: request.url(options), + method: 'get', +}) + +request.definition = { + methods: ["get","head"], + url: '/admin/password-reset/request', +} satisfies RouteDefinition<["get","head"]> + +/** +* @see \Filament\Auth\Pages\PasswordReset\RequestPasswordReset::__invoke +* @see vendor/filament/filament/src/Auth/Pages/PasswordReset/RequestPasswordReset.php:7 +* @route '/admin/password-reset/request' +*/ +request.url = (options?: RouteQueryOptions) => { + return request.definition.url + queryParams(options) +} + +/** +* @see \Filament\Auth\Pages\PasswordReset\RequestPasswordReset::__invoke +* @see vendor/filament/filament/src/Auth/Pages/PasswordReset/RequestPasswordReset.php:7 +* @route '/admin/password-reset/request' +*/ +request.get = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: request.url(options), + method: 'get', +}) + +/** +* @see \Filament\Auth\Pages\PasswordReset\RequestPasswordReset::__invoke +* @see vendor/filament/filament/src/Auth/Pages/PasswordReset/RequestPasswordReset.php:7 +* @route '/admin/password-reset/request' +*/ +request.head = (options?: RouteQueryOptions): RouteDefinition<'head'> => ({ + url: request.url(options), + method: 'head', +}) + +/** +* @see \Filament\Auth\Pages\PasswordReset\RequestPasswordReset::__invoke +* @see vendor/filament/filament/src/Auth/Pages/PasswordReset/RequestPasswordReset.php:7 +* @route '/admin/password-reset/request' +*/ +const requestForm = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: request.url(options), + method: 'get', +}) + +/** +* @see \Filament\Auth\Pages\PasswordReset\RequestPasswordReset::__invoke +* @see vendor/filament/filament/src/Auth/Pages/PasswordReset/RequestPasswordReset.php:7 +* @route '/admin/password-reset/request' +*/ +requestForm.get = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: request.url(options), + method: 'get', +}) + +/** +* @see \Filament\Auth\Pages\PasswordReset\RequestPasswordReset::__invoke +* @see vendor/filament/filament/src/Auth/Pages/PasswordReset/RequestPasswordReset.php:7 +* @route '/admin/password-reset/request' +*/ +requestForm.head = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: request.url({ + [options?.mergeQuery ? 'mergeQuery' : 'query']: { + _method: 'HEAD', + ...(options?.query ?? options?.mergeQuery ?? {}), + } + }), + method: 'get', +}) + +request.form = requestForm + +/** +* @see \Filament\Auth\Pages\PasswordReset\ResetPassword::__invoke +* @see vendor/filament/filament/src/Auth/Pages/PasswordReset/ResetPassword.php:7 +* @route '/admin/password-reset/reset' +*/ +export const reset = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: reset.url(options), + method: 'get', +}) + +reset.definition = { + methods: ["get","head"], + url: '/admin/password-reset/reset', +} satisfies RouteDefinition<["get","head"]> + +/** +* @see \Filament\Auth\Pages\PasswordReset\ResetPassword::__invoke +* @see vendor/filament/filament/src/Auth/Pages/PasswordReset/ResetPassword.php:7 +* @route '/admin/password-reset/reset' +*/ +reset.url = (options?: RouteQueryOptions) => { + return reset.definition.url + queryParams(options) +} + +/** +* @see \Filament\Auth\Pages\PasswordReset\ResetPassword::__invoke +* @see vendor/filament/filament/src/Auth/Pages/PasswordReset/ResetPassword.php:7 +* @route '/admin/password-reset/reset' +*/ +reset.get = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: reset.url(options), + method: 'get', +}) + +/** +* @see \Filament\Auth\Pages\PasswordReset\ResetPassword::__invoke +* @see vendor/filament/filament/src/Auth/Pages/PasswordReset/ResetPassword.php:7 +* @route '/admin/password-reset/reset' +*/ +reset.head = (options?: RouteQueryOptions): RouteDefinition<'head'> => ({ + url: reset.url(options), + method: 'head', +}) + +/** +* @see \Filament\Auth\Pages\PasswordReset\ResetPassword::__invoke +* @see vendor/filament/filament/src/Auth/Pages/PasswordReset/ResetPassword.php:7 +* @route '/admin/password-reset/reset' +*/ +const resetForm = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: reset.url(options), + method: 'get', +}) + +/** +* @see \Filament\Auth\Pages\PasswordReset\ResetPassword::__invoke +* @see vendor/filament/filament/src/Auth/Pages/PasswordReset/ResetPassword.php:7 +* @route '/admin/password-reset/reset' +*/ +resetForm.get = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: reset.url(options), + method: 'get', +}) + +/** +* @see \Filament\Auth\Pages\PasswordReset\ResetPassword::__invoke +* @see vendor/filament/filament/src/Auth/Pages/PasswordReset/ResetPassword.php:7 +* @route '/admin/password-reset/reset' +*/ +resetForm.head = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: reset.url({ + [options?.mergeQuery ? 'mergeQuery' : 'query']: { + _method: 'HEAD', + ...(options?.query ?? options?.mergeQuery ?? {}), + } + }), + method: 'get', +}) + +reset.form = resetForm + +const passwordReset = { + request: Object.assign(request, request), + reset: Object.assign(reset, reset), +} + +export default passwordReset \ No newline at end of file diff --git a/resources/js/routes/password/index.ts b/resources/js/routes/password/index.ts index d3f2ce9..97815f4 100644 --- a/resources/js/routes/password/index.ts +++ b/resources/js/routes/password/index.ts @@ -1,5 +1,297 @@ import { queryParams, type RouteQueryOptions, type RouteDefinition, type RouteFormDefinition, applyUrlDefaults } from './../../wayfinder' import confirmD7e05f from './confirm' +/** +* @see \App\Http\Controllers\Auth\PasswordResetLinkController::request +* @see app/Http/Controllers/Auth/PasswordResetLinkController.php:17 +* @route '/forgot-password' +*/ +export const request = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: request.url(options), + method: 'get', +}) + +request.definition = { + methods: ["get","head"], + url: '/forgot-password', +} satisfies RouteDefinition<["get","head"]> + +/** +* @see \App\Http\Controllers\Auth\PasswordResetLinkController::request +* @see app/Http/Controllers/Auth/PasswordResetLinkController.php:17 +* @route '/forgot-password' +*/ +request.url = (options?: RouteQueryOptions) => { + return request.definition.url + queryParams(options) +} + +/** +* @see \App\Http\Controllers\Auth\PasswordResetLinkController::request +* @see app/Http/Controllers/Auth/PasswordResetLinkController.php:17 +* @route '/forgot-password' +*/ +request.get = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: request.url(options), + method: 'get', +}) + +/** +* @see \App\Http\Controllers\Auth\PasswordResetLinkController::request +* @see app/Http/Controllers/Auth/PasswordResetLinkController.php:17 +* @route '/forgot-password' +*/ +request.head = (options?: RouteQueryOptions): RouteDefinition<'head'> => ({ + url: request.url(options), + method: 'head', +}) + +/** +* @see \App\Http\Controllers\Auth\PasswordResetLinkController::request +* @see app/Http/Controllers/Auth/PasswordResetLinkController.php:17 +* @route '/forgot-password' +*/ +const requestForm = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: request.url(options), + method: 'get', +}) + +/** +* @see \App\Http\Controllers\Auth\PasswordResetLinkController::request +* @see app/Http/Controllers/Auth/PasswordResetLinkController.php:17 +* @route '/forgot-password' +*/ +requestForm.get = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: request.url(options), + method: 'get', +}) + +/** +* @see \App\Http\Controllers\Auth\PasswordResetLinkController::request +* @see app/Http/Controllers/Auth/PasswordResetLinkController.php:17 +* @route '/forgot-password' +*/ +requestForm.head = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: request.url({ + [options?.mergeQuery ? 'mergeQuery' : 'query']: { + _method: 'HEAD', + ...(options?.query ?? options?.mergeQuery ?? {}), + } + }), + method: 'get', +}) + +request.form = requestForm + +/** +* @see \App\Http\Controllers\Auth\NewPasswordController::reset +* @see app/Http/Controllers/Auth/NewPasswordController.php:23 +* @route '/reset-password/{token}' +*/ +export const reset = (args: { token: string | number } | [token: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: reset.url(args, options), + method: 'get', +}) + +reset.definition = { + methods: ["get","head"], + url: '/reset-password/{token}', +} satisfies RouteDefinition<["get","head"]> + +/** +* @see \App\Http\Controllers\Auth\NewPasswordController::reset +* @see app/Http/Controllers/Auth/NewPasswordController.php:23 +* @route '/reset-password/{token}' +*/ +reset.url = (args: { token: string | number } | [token: string | number ] | string | number, options?: RouteQueryOptions) => { + if (typeof args === 'string' || typeof args === 'number') { + args = { token: args } + } + + if (Array.isArray(args)) { + args = { + token: args[0], + } + } + + args = applyUrlDefaults(args) + + const parsedArgs = { + token: args.token, + } + + return reset.definition.url + .replace('{token}', parsedArgs.token.toString()) + .replace(/\/+$/, '') + queryParams(options) +} + +/** +* @see \App\Http\Controllers\Auth\NewPasswordController::reset +* @see app/Http/Controllers/Auth/NewPasswordController.php:23 +* @route '/reset-password/{token}' +*/ +reset.get = (args: { token: string | number } | [token: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: reset.url(args, options), + method: 'get', +}) + +/** +* @see \App\Http\Controllers\Auth\NewPasswordController::reset +* @see app/Http/Controllers/Auth/NewPasswordController.php:23 +* @route '/reset-password/{token}' +*/ +reset.head = (args: { token: string | number } | [token: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'head'> => ({ + url: reset.url(args, options), + method: 'head', +}) + +/** +* @see \App\Http\Controllers\Auth\NewPasswordController::reset +* @see app/Http/Controllers/Auth/NewPasswordController.php:23 +* @route '/reset-password/{token}' +*/ +const resetForm = (args: { token: string | number } | [token: string | number ] | string | number, options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: reset.url(args, options), + method: 'get', +}) + +/** +* @see \App\Http\Controllers\Auth\NewPasswordController::reset +* @see app/Http/Controllers/Auth/NewPasswordController.php:23 +* @route '/reset-password/{token}' +*/ +resetForm.get = (args: { token: string | number } | [token: string | number ] | string | number, options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: reset.url(args, options), + method: 'get', +}) + +/** +* @see \App\Http\Controllers\Auth\NewPasswordController::reset +* @see app/Http/Controllers/Auth/NewPasswordController.php:23 +* @route '/reset-password/{token}' +*/ +resetForm.head = (args: { token: string | number } | [token: string | number ] | string | number, options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: reset.url(args, { + [options?.mergeQuery ? 'mergeQuery' : 'query']: { + _method: 'HEAD', + ...(options?.query ?? options?.mergeQuery ?? {}), + } + }), + method: 'get', +}) + +reset.form = resetForm + +/** +* @see \App\Http\Controllers\Auth\PasswordResetLinkController::email +* @see app/Http/Controllers/Auth/PasswordResetLinkController.php:29 +* @route '/forgot-password' +*/ +export const email = (options?: RouteQueryOptions): RouteDefinition<'post'> => ({ + url: email.url(options), + method: 'post', +}) + +email.definition = { + methods: ["post"], + url: '/forgot-password', +} satisfies RouteDefinition<["post"]> + +/** +* @see \App\Http\Controllers\Auth\PasswordResetLinkController::email +* @see app/Http/Controllers/Auth/PasswordResetLinkController.php:29 +* @route '/forgot-password' +*/ +email.url = (options?: RouteQueryOptions) => { + return email.definition.url + queryParams(options) +} + +/** +* @see \App\Http\Controllers\Auth\PasswordResetLinkController::email +* @see app/Http/Controllers/Auth/PasswordResetLinkController.php:29 +* @route '/forgot-password' +*/ +email.post = (options?: RouteQueryOptions): RouteDefinition<'post'> => ({ + url: email.url(options), + method: 'post', +}) + +/** +* @see \App\Http\Controllers\Auth\PasswordResetLinkController::email +* @see app/Http/Controllers/Auth/PasswordResetLinkController.php:29 +* @route '/forgot-password' +*/ +const emailForm = (options?: RouteQueryOptions): RouteFormDefinition<'post'> => ({ + action: email.url(options), + method: 'post', +}) + +/** +* @see \App\Http\Controllers\Auth\PasswordResetLinkController::email +* @see app/Http/Controllers/Auth/PasswordResetLinkController.php:29 +* @route '/forgot-password' +*/ +emailForm.post = (options?: RouteQueryOptions): RouteFormDefinition<'post'> => ({ + action: email.url(options), + method: 'post', +}) + +email.form = emailForm + +/** +* @see \App\Http\Controllers\Auth\NewPasswordController::store +* @see app/Http/Controllers/Auth/NewPasswordController.php:36 +* @route '/reset-password' +*/ +export const store = (options?: RouteQueryOptions): RouteDefinition<'post'> => ({ + url: store.url(options), + method: 'post', +}) + +store.definition = { + methods: ["post"], + url: '/reset-password', +} satisfies RouteDefinition<["post"]> + +/** +* @see \App\Http\Controllers\Auth\NewPasswordController::store +* @see app/Http/Controllers/Auth/NewPasswordController.php:36 +* @route '/reset-password' +*/ +store.url = (options?: RouteQueryOptions) => { + return store.definition.url + queryParams(options) +} + +/** +* @see \App\Http\Controllers\Auth\NewPasswordController::store +* @see app/Http/Controllers/Auth/NewPasswordController.php:36 +* @route '/reset-password' +*/ +store.post = (options?: RouteQueryOptions): RouteDefinition<'post'> => ({ + url: store.url(options), + method: 'post', +}) + +/** +* @see \App\Http\Controllers\Auth\NewPasswordController::store +* @see app/Http/Controllers/Auth/NewPasswordController.php:36 +* @route '/reset-password' +*/ +const storeForm = (options?: RouteQueryOptions): RouteFormDefinition<'post'> => ({ + action: store.url(options), + method: 'post', +}) + +/** +* @see \App\Http\Controllers\Auth\NewPasswordController::store +* @see app/Http/Controllers/Auth/NewPasswordController.php:36 +* @route '/reset-password' +*/ +storeForm.post = (options?: RouteQueryOptions): RouteFormDefinition<'post'> => ({ + action: store.url(options), + method: 'post', +}) + +store.form = storeForm + /** * @see \Laravel\Fortify\Http\Controllers\ConfirmablePasswordController::confirm * @see vendor/laravel/fortify/src/Http/Controllers/ConfirmablePasswordController.php:40 @@ -309,307 +601,15 @@ updateForm.put = (options?: RouteQueryOptions): RouteFormDefinition<'post'> => ( update.form = updateForm -/** -* @see \App\Http\Controllers\Auth\PasswordResetLinkController::request -* @see app/Http/Controllers/Auth/PasswordResetLinkController.php:17 -* @route '/forgot-password' -*/ -export const request = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({ - url: request.url(options), - method: 'get', -}) - -request.definition = { - methods: ["get","head"], - url: '/forgot-password', -} satisfies RouteDefinition<["get","head"]> - -/** -* @see \App\Http\Controllers\Auth\PasswordResetLinkController::request -* @see app/Http/Controllers/Auth/PasswordResetLinkController.php:17 -* @route '/forgot-password' -*/ -request.url = (options?: RouteQueryOptions) => { - return request.definition.url + queryParams(options) -} - -/** -* @see \App\Http\Controllers\Auth\PasswordResetLinkController::request -* @see app/Http/Controllers/Auth/PasswordResetLinkController.php:17 -* @route '/forgot-password' -*/ -request.get = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({ - url: request.url(options), - method: 'get', -}) - -/** -* @see \App\Http\Controllers\Auth\PasswordResetLinkController::request -* @see app/Http/Controllers/Auth/PasswordResetLinkController.php:17 -* @route '/forgot-password' -*/ -request.head = (options?: RouteQueryOptions): RouteDefinition<'head'> => ({ - url: request.url(options), - method: 'head', -}) - -/** -* @see \App\Http\Controllers\Auth\PasswordResetLinkController::request -* @see app/Http/Controllers/Auth/PasswordResetLinkController.php:17 -* @route '/forgot-password' -*/ -const requestForm = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ - action: request.url(options), - method: 'get', -}) - -/** -* @see \App\Http\Controllers\Auth\PasswordResetLinkController::request -* @see app/Http/Controllers/Auth/PasswordResetLinkController.php:17 -* @route '/forgot-password' -*/ -requestForm.get = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ - action: request.url(options), - method: 'get', -}) - -/** -* @see \App\Http\Controllers\Auth\PasswordResetLinkController::request -* @see app/Http/Controllers/Auth/PasswordResetLinkController.php:17 -* @route '/forgot-password' -*/ -requestForm.head = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ - action: request.url({ - [options?.mergeQuery ? 'mergeQuery' : 'query']: { - _method: 'HEAD', - ...(options?.query ?? options?.mergeQuery ?? {}), - } - }), - method: 'get', -}) - -request.form = requestForm - -/** -* @see \App\Http\Controllers\Auth\PasswordResetLinkController::email -* @see app/Http/Controllers/Auth/PasswordResetLinkController.php:29 -* @route '/forgot-password' -*/ -export const email = (options?: RouteQueryOptions): RouteDefinition<'post'> => ({ - url: email.url(options), - method: 'post', -}) - -email.definition = { - methods: ["post"], - url: '/forgot-password', -} satisfies RouteDefinition<["post"]> - -/** -* @see \App\Http\Controllers\Auth\PasswordResetLinkController::email -* @see app/Http/Controllers/Auth/PasswordResetLinkController.php:29 -* @route '/forgot-password' -*/ -email.url = (options?: RouteQueryOptions) => { - return email.definition.url + queryParams(options) -} - -/** -* @see \App\Http\Controllers\Auth\PasswordResetLinkController::email -* @see app/Http/Controllers/Auth/PasswordResetLinkController.php:29 -* @route '/forgot-password' -*/ -email.post = (options?: RouteQueryOptions): RouteDefinition<'post'> => ({ - url: email.url(options), - method: 'post', -}) - -/** -* @see \App\Http\Controllers\Auth\PasswordResetLinkController::email -* @see app/Http/Controllers/Auth/PasswordResetLinkController.php:29 -* @route '/forgot-password' -*/ -const emailForm = (options?: RouteQueryOptions): RouteFormDefinition<'post'> => ({ - action: email.url(options), - method: 'post', -}) - -/** -* @see \App\Http\Controllers\Auth\PasswordResetLinkController::email -* @see app/Http/Controllers/Auth/PasswordResetLinkController.php:29 -* @route '/forgot-password' -*/ -emailForm.post = (options?: RouteQueryOptions): RouteFormDefinition<'post'> => ({ - action: email.url(options), - method: 'post', -}) - -email.form = emailForm - -/** -* @see \App\Http\Controllers\Auth\NewPasswordController::reset -* @see app/Http/Controllers/Auth/NewPasswordController.php:23 -* @route '/reset-password/{token}' -*/ -export const reset = (args: { token: string | number } | [token: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'get'> => ({ - url: reset.url(args, options), - method: 'get', -}) - -reset.definition = { - methods: ["get","head"], - url: '/reset-password/{token}', -} satisfies RouteDefinition<["get","head"]> - -/** -* @see \App\Http\Controllers\Auth\NewPasswordController::reset -* @see app/Http/Controllers/Auth/NewPasswordController.php:23 -* @route '/reset-password/{token}' -*/ -reset.url = (args: { token: string | number } | [token: string | number ] | string | number, options?: RouteQueryOptions) => { - if (typeof args === 'string' || typeof args === 'number') { - args = { token: args } - } - - if (Array.isArray(args)) { - args = { - token: args[0], - } - } - - args = applyUrlDefaults(args) - - const parsedArgs = { - token: args.token, - } - - return reset.definition.url - .replace('{token}', parsedArgs.token.toString()) - .replace(/\/+$/, '') + queryParams(options) -} - -/** -* @see \App\Http\Controllers\Auth\NewPasswordController::reset -* @see app/Http/Controllers/Auth/NewPasswordController.php:23 -* @route '/reset-password/{token}' -*/ -reset.get = (args: { token: string | number } | [token: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'get'> => ({ - url: reset.url(args, options), - method: 'get', -}) - -/** -* @see \App\Http\Controllers\Auth\NewPasswordController::reset -* @see app/Http/Controllers/Auth/NewPasswordController.php:23 -* @route '/reset-password/{token}' -*/ -reset.head = (args: { token: string | number } | [token: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'head'> => ({ - url: reset.url(args, options), - method: 'head', -}) - -/** -* @see \App\Http\Controllers\Auth\NewPasswordController::reset -* @see app/Http/Controllers/Auth/NewPasswordController.php:23 -* @route '/reset-password/{token}' -*/ -const resetForm = (args: { token: string | number } | [token: string | number ] | string | number, options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ - action: reset.url(args, options), - method: 'get', -}) - -/** -* @see \App\Http\Controllers\Auth\NewPasswordController::reset -* @see app/Http/Controllers/Auth/NewPasswordController.php:23 -* @route '/reset-password/{token}' -*/ -resetForm.get = (args: { token: string | number } | [token: string | number ] | string | number, options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ - action: reset.url(args, options), - method: 'get', -}) - -/** -* @see \App\Http\Controllers\Auth\NewPasswordController::reset -* @see app/Http/Controllers/Auth/NewPasswordController.php:23 -* @route '/reset-password/{token}' -*/ -resetForm.head = (args: { token: string | number } | [token: string | number ] | string | number, options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ - action: reset.url(args, { - [options?.mergeQuery ? 'mergeQuery' : 'query']: { - _method: 'HEAD', - ...(options?.query ?? options?.mergeQuery ?? {}), - } - }), - method: 'get', -}) - -reset.form = resetForm - -/** -* @see \App\Http\Controllers\Auth\NewPasswordController::store -* @see app/Http/Controllers/Auth/NewPasswordController.php:36 -* @route '/reset-password' -*/ -export const store = (options?: RouteQueryOptions): RouteDefinition<'post'> => ({ - url: store.url(options), - method: 'post', -}) - -store.definition = { - methods: ["post"], - url: '/reset-password', -} satisfies RouteDefinition<["post"]> - -/** -* @see \App\Http\Controllers\Auth\NewPasswordController::store -* @see app/Http/Controllers/Auth/NewPasswordController.php:36 -* @route '/reset-password' -*/ -store.url = (options?: RouteQueryOptions) => { - return store.definition.url + queryParams(options) -} - -/** -* @see \App\Http\Controllers\Auth\NewPasswordController::store -* @see app/Http/Controllers/Auth/NewPasswordController.php:36 -* @route '/reset-password' -*/ -store.post = (options?: RouteQueryOptions): RouteDefinition<'post'> => ({ - url: store.url(options), - method: 'post', -}) - -/** -* @see \App\Http\Controllers\Auth\NewPasswordController::store -* @see app/Http/Controllers/Auth/NewPasswordController.php:36 -* @route '/reset-password' -*/ -const storeForm = (options?: RouteQueryOptions): RouteFormDefinition<'post'> => ({ - action: store.url(options), - method: 'post', -}) - -/** -* @see \App\Http\Controllers\Auth\NewPasswordController::store -* @see app/Http/Controllers/Auth/NewPasswordController.php:36 -* @route '/reset-password' -*/ -storeForm.post = (options?: RouteQueryOptions): RouteFormDefinition<'post'> => ({ - action: store.url(options), - method: 'post', -}) - -store.form = storeForm - const password = { + request: Object.assign(request, request), + reset: Object.assign(reset, reset), + email: Object.assign(email, email), + store: Object.assign(store, store), confirm: Object.assign(confirm, confirmD7e05f), confirmation: Object.assign(confirmation, confirmation), edit: Object.assign(edit, edit), update: Object.assign(update, update), - request: Object.assign(request, request), - email: Object.assign(email, email), - reset: Object.assign(reset, reset), - store: Object.assign(store, store), } export default password \ No newline at end of file