diff --git a/.ai-agent.md b/.ai-agent.md index 1d2c99e..19da209 100644 --- a/.ai-agent.md +++ b/.ai-agent.md @@ -1,6 +1,6 @@ Tu es un agent IA senior specialise en Laravel, Filament, React et Tailwind. -Tu respectes STRICTEMENT les regles du fichier .ai-rules.md. +Tu respectes STRICTEMENT les regles du fichier .ai-rules.md et tu parcours bien le ficher .PROJECT_STRUCTURE.md pour avoir le contexte du projet. Ton role : - proposer des solutions simples et maintenables diff --git a/.claude/settings.local.json b/.claude/settings.local.json index df5c779..a9989ad 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -3,7 +3,13 @@ "allow": [ "mcp__laravel-boost__application-info", "mcp__laravel-boost__database-schema", - "mcp__laravel-boost__list-routes" + "mcp__laravel-boost__list-routes", + "Bash(php artisan make:migration:*)", + "mcp__laravel-boost__search-docs", + "Bash(vendor/bin/pint:*)", + "Bash(php artisan test:*)", + "Bash(php artisan migrate:*)", + "Bash(php artisan make:filament-relation-manager:*)" ] }, "enableAllProjectMcpServers": true, diff --git a/app/Events/MemberRegistered.php b/app/Events/MemberRegistered.php index c8e5ddb..2cc8592 100644 --- a/app/Events/MemberRegistered.php +++ b/app/Events/MemberRegistered.php @@ -2,35 +2,13 @@ namespace App\Events; -use Illuminate\Broadcasting\Channel; -use Illuminate\Broadcasting\InteractsWithSockets; -use Illuminate\Broadcasting\PresenceChannel; -use Illuminate\Broadcasting\PrivateChannel; -use Illuminate\Contracts\Broadcasting\ShouldBroadcast; +use App\Models\Member; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; class MemberRegistered { - use Dispatchable, InteractsWithSockets, SerializesModels; + use Dispatchable, SerializesModels; - /** - * Create a new event instance. - */ - public function __construct() - { - // - } - - /** - * Get the channels the event should broadcast on. - * - * @return array - */ - public function broadcastOn(): array - { - return [ - new PrivateChannel('channel-name'), - ]; - } + public function __construct(public readonly Member $member) {} } diff --git a/app/Events/MemberValidated.php b/app/Events/MemberValidated.php index 84026a4..e04e46c 100644 --- a/app/Events/MemberValidated.php +++ b/app/Events/MemberValidated.php @@ -2,35 +2,13 @@ namespace App\Events; -use Illuminate\Broadcasting\Channel; -use Illuminate\Broadcasting\InteractsWithSockets; -use Illuminate\Broadcasting\PresenceChannel; -use Illuminate\Broadcasting\PrivateChannel; -use Illuminate\Contracts\Broadcasting\ShouldBroadcast; +use App\Models\Member; use Illuminate\Foundation\Events\Dispatchable; use Illuminate\Queue\SerializesModels; class MemberValidated { - use Dispatchable, InteractsWithSockets, SerializesModels; + use Dispatchable, SerializesModels; - /** - * Create a new event instance. - */ - public function __construct() - { - // - } - - /** - * Get the channels the event should broadcast on. - * - * @return array - */ - public function broadcastOn(): array - { - return [ - new PrivateChannel('channel-name'), - ]; - } + public function __construct(public readonly Member $member) {} } diff --git a/app/Filament/Resources/Members/MemberResource.php b/app/Filament/Resources/Members/MemberResource.php index 8bbdb11..6b9c7cc 100644 --- a/app/Filament/Resources/Members/MemberResource.php +++ b/app/Filament/Resources/Members/MemberResource.php @@ -36,7 +36,7 @@ class MemberResource extends Resource public static function getRelations(): array { return [ - // + RelationManagers\MembershipsRelationManager::class, ]; } @@ -65,5 +65,4 @@ class MemberResource extends Resource MemberCount::class, ]; } - } diff --git a/app/Filament/Resources/Members/RelationManagers/MembershipsRelationManager.php b/app/Filament/Resources/Members/RelationManagers/MembershipsRelationManager.php new file mode 100644 index 0000000..e1907f8 --- /dev/null +++ b/app/Filament/Resources/Members/RelationManagers/MembershipsRelationManager.php @@ -0,0 +1,78 @@ +recordTitleAttribute('start_date') + ->columns([ + TextColumn::make('start_date') + ->label(Membership::getAttributeLabel('start_date')) + ->date() + ->sortable(), + TextColumn::make('end_date') + ->label(Membership::getAttributeLabel('end_date')) + ->date() + ->sortable(), + TextColumn::make('status') + ->label(Membership::getAttributeLabel('status')) + ->formatStateUsing(fn (string $state) => Membership::getAttributeLabel($state)) + ->badge() + ->color(fn (string $state): string => match ($state) { + 'active' => 'success', + 'expired' => 'danger', + 'pending' => 'warning', + }), + TextColumn::make('package.name') + ->label(Membership::getAttributeLabel('package_id')), + TextColumn::make('amount') + ->label(Membership::getAttributeLabel('amount')) + ->money('EUR') + ->sortable(), + TextColumn::make('payment_status') + ->label(Membership::getAttributeLabel('payment_status')) + ->formatStateUsing(fn (string $state) => Membership::getAttributeLabel($state)) + ->badge() + ->color(fn (string $state): string => match ($state) { + 'paid' => 'success', + 'unpaid' => 'danger', + 'partial' => 'warning', + }), + TextColumn::make('created_at') + ->label(Membership::getAttributeLabel('created_at')) + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + ]) + ->defaultSort('start_date', 'desc') + ->headerActions([ + CreateAction::make(), + ]) + ->recordActions([ + EditAction::make(), + DeleteAction::make(), + ]) + ->toolbarActions([ + BulkActionGroup::make([ + DeleteBulkAction::make(), + ]), + ]); + } +} diff --git a/app/Filament/Resources/NotificationTemplates/NotificationTemplateResource.php b/app/Filament/Resources/NotificationTemplates/NotificationTemplateResource.php new file mode 100644 index 0000000..de106e6 --- /dev/null +++ b/app/Filament/Resources/NotificationTemplates/NotificationTemplateResource.php @@ -0,0 +1,58 @@ + ListNotificationTemplates::route('/'), + 'create' => CreateNotificationTemplate::route('/create'), + 'edit' => EditNotificationTemplate::route('/{record}/edit'), + ]; + } +} diff --git a/app/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.php b/app/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.php new file mode 100644 index 0000000..502f68a --- /dev/null +++ b/app/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.php @@ -0,0 +1,11 @@ +components([ + Section::make(fn (?NotificationTemplate $record) => $record?->name ?? NotificationTemplate::getAttributeLabel('name')) + ->afterHeader([ + Toggle::make('is_active') + ->label(NotificationTemplate::getAttributeLabel('is_active')) + ->default(true), + ]) + ->schema([ + TextInput::make('name') + ->label(NotificationTemplate::getAttributeLabel('name')) + ->required(), + TextInput::make('identifier') + ->label(NotificationTemplate::getAttributeLabel('identifier')) + ->required() + ->disabledOn('edit'), + TextInput::make('subject') + ->label(NotificationTemplate::getAttributeLabel('subject')) + ->required() + ->helperText('Variables : {member_name}, {expiry_date}'), + RichEditor::make('body') + ->label(NotificationTemplate::getAttributeLabel('body')) + ->required() + ->helperText('Variables : {member_name}, {expiry_date}') + ->columnSpanFull(), + ]), + ]); + } +} diff --git a/app/Filament/Resources/NotificationTemplates/Tables/NotificationTemplatesTable.php b/app/Filament/Resources/NotificationTemplates/Tables/NotificationTemplatesTable.php new file mode 100644 index 0000000..e3da9d2 --- /dev/null +++ b/app/Filament/Resources/NotificationTemplates/Tables/NotificationTemplatesTable.php @@ -0,0 +1,50 @@ +columns([ + TextColumn::make('name') + ->label(NotificationTemplate::getAttributeLabel('name')) + ->searchable(), + TextColumn::make('identifier') + ->label(NotificationTemplate::getAttributeLabel('identifier')) + ->searchable(), + TextColumn::make('subject') + ->label(NotificationTemplate::getAttributeLabel('subject')) + ->searchable(), + IconColumn::make('is_active') + ->label(NotificationTemplate::getAttributeLabel('is_active')) + ->boolean(), + TextColumn::make('updated_at') + ->dateTime() + ->sortable() + ->toggleable(isToggledHiddenByDefault: true), + ]) + ->filters([]) + ->recordActions([ + EditAction::make(), + ]) + ->toolbarActions([ + BulkActionGroup::make([ + DeleteBulkAction::make(), + ForceDeleteBulkAction::make(), + RestoreBulkAction::make(), + ]), + ]); + } +} diff --git a/app/Jobs/SendSubscriptionExpiredPhase1Notifications.php b/app/Jobs/SendSubscriptionExpiredPhase1Notifications.php index c9291c8..b706b60 100644 --- a/app/Jobs/SendSubscriptionExpiredPhase1Notifications.php +++ b/app/Jobs/SendSubscriptionExpiredPhase1Notifications.php @@ -3,6 +3,7 @@ namespace App\Jobs; use App\Models\Member; +use App\Models\NotificationTemplate; use App\Notifications\SubscriptionExpiredPhase1; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Queue\Queueable; @@ -11,22 +12,21 @@ class SendSubscriptionExpiredPhase1Notifications implements ShouldQueue { use Queueable; - /** - * Create a new job instance. - */ - public function __construct() - { - } + public function __construct() {} - /** - * Execute the job. - */ public function handle(): void { - Member::isExpired() - ->chunk(100, function ($members) { + $template = NotificationTemplate::findByIdentifier('subscription_expired_phase1'); + + if (! $template) { + return; + } + + Member::query() + ->whereHas('memberships', fn ($query) => $query->where('status', 'expired')) + ->chunk(100, function ($members) use ($template) { foreach ($members as $member) { - $member->notify(new SubscriptionExpiredPhase1()); + $member->notify(new SubscriptionExpiredPhase1($template)); } }); } diff --git a/app/Listeners/NotifiyMemberOfValidation.php b/app/Listeners/NotifiyMemberOfValidation.php deleted file mode 100644 index bd1847e..0000000 --- a/app/Listeners/NotifiyMemberOfValidation.php +++ /dev/null @@ -1,26 +0,0 @@ -first(); - - $admin->notify(new AdminNewUserPending($event->user)); - - } -} diff --git a/app/Models/Member.php b/app/Models/Member.php index efc7d4b..d159cf8 100644 --- a/app/Models/Member.php +++ b/app/Models/Member.php @@ -2,7 +2,6 @@ namespace App\Models; -use App\Enums\IspconfigType; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -46,6 +45,7 @@ use Illuminate\Notifications\Notifiable; * @property-read \Illuminate\Notifications\DatabaseNotificationCollection $notifications * @property-read int|null $notifications_count * @property-read \App\Models\User|null $user + * * @method static \Database\Factories\MemberFactory factory($count = null, $state = []) * @method static \Illuminate\Database\Eloquent\Builder|Member newModelQuery() * @method static \Illuminate\Database\Eloquent\Builder|Member newQuery() @@ -75,11 +75,13 @@ use Illuminate\Notifications\Notifiable; * @method static \Illuminate\Database\Eloquent\Builder|Member whereUserId($value) * @method static \Illuminate\Database\Eloquent\Builder|Member whereWebsiteUrl($value) * @method static \Illuminate\Database\Eloquent\Builder|Member whereZipcode($value) + * * @mixin \Eloquent */ class Member extends Model { use HasFactory, Notifiable; + protected $fillable = [ 'user_id', 'dolibarr_id', @@ -100,7 +102,7 @@ class Member extends Model 'phone1', 'phone2', 'public_membership', - 'website_url' + 'website_url', ]; public static function getAttributeLabel(string $attribute): string @@ -113,10 +115,11 @@ class Member extends Model return "{$this->firstname} {$this->lastname}"; } - public function getRetzienEmailAttribute(): string + public function getRetzienEmailAttribute(): ?string { - $emails = explode(';', $this->email); - return collect($emails)->filter(fn($email) => str_contains($email, '@retzien.fr'))->first(); + $emails = explode(';', $this->email); + + return collect($emails)->filter(fn ($email) => str_contains($email, '@retzien.fr'))->first(); } public function user(): BelongsTo @@ -152,6 +155,7 @@ class Member extends Model public function hasService(string $serviceIdentifier): bool { $membership = $this->lastMembership(); + return $membership->services()->where('identifier', $serviceIdentifier)->exists(); } @@ -159,7 +163,7 @@ class Member extends Model { // Member ayant leur dernière adhésion non renouvellée de puis plus d'un mois $lastMembership = $this->lastMembership(); + return $lastMembership->status === 'expired' || $lastMembership->created_at->addMonths(1) < now(); } - } diff --git a/app/Models/NotificationTemplate.php b/app/Models/NotificationTemplate.php new file mode 100644 index 0000000..b45c980 --- /dev/null +++ b/app/Models/NotificationTemplate.php @@ -0,0 +1,64 @@ + + */ + protected function casts(): array + { + return [ + 'variables' => 'array', + 'is_active' => 'boolean', + ]; + } + + public static function getAttributeLabel(string $attribute): string + { + return __('notification_templates.fields.'.$attribute); + } + + public static function findByIdentifier(string $identifier): ?self + { + return self::query() + ->where('identifier', $identifier) + ->where('is_active', true) + ->first(); + } + + public function renderSubject(array $vars): string + { + return $this->replacePlaceholders($this->subject, $vars); + } + + public function renderBody(array $vars): string + { + return $this->replacePlaceholders($this->body, $vars); + } + + private function replacePlaceholders(string $text, array $vars): string + { + foreach ($vars as $key => $value) { + $text = str_replace('{'.$key.'}', (string) $value, $text); + } + + return $text; + } +} diff --git a/app/Notifications/SubscriptionExpiredPhase1.php b/app/Notifications/SubscriptionExpiredPhase1.php index f27d366..ee9c634 100644 --- a/app/Notifications/SubscriptionExpiredPhase1.php +++ b/app/Notifications/SubscriptionExpiredPhase1.php @@ -2,6 +2,7 @@ namespace App\Notifications; +use App\Models\NotificationTemplate; use Illuminate\Bus\Queueable; use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Notifications\Messages\MailMessage; @@ -11,17 +12,9 @@ class SubscriptionExpiredPhase1 extends Notification implements ShouldQueue { use Queueable; - /** - * Create a new notification instance. - */ - public function __construct() - { - // - } + public function __construct(public readonly NotificationTemplate $template) {} /** - * Get the notification's delivery channels. - * * @return array */ public function via(object $notifiable): array @@ -29,30 +22,27 @@ class SubscriptionExpiredPhase1 extends Notification implements ShouldQueue return ['mail']; } - /** - * Get the mail representation of the notification. - */ public function toMail(object $notifiable): MailMessage { - //@todo: créer template générique + espace dans le BO pour alimenter les texte + $lastMembership = $notifiable->memberships()->latest()->first(); + + $vars = [ + 'member_name' => $notifiable->full_name, + 'expiry_date' => $lastMembership?->end_date ?? '', + ]; + return (new MailMessage) - ->subject('Votre adhésion est expirée') - ->greeting('Bonjour ' . $notifiable->name) - ->line('Votre adhésion est arrivée à expiration.') - ->line('Pour continuer à profiter nos services, merci de le renouveler.') - ->action('Renouveler mon adhésion', url('/devenir-membre')) - ->line('Merci pour votre confiance.'); + ->subject($this->template->renderSubject($vars)) + ->view('notifications.mail-template', [ + 'body' => $this->template->renderBody($vars), + ]); } /** - * Get the array representation of the notification. - * * @return array */ public function toArray(object $notifiable): array { - return [ - // - ]; + return []; } } diff --git a/app/Policies/NotificationTemplatePolicy.php b/app/Policies/NotificationTemplatePolicy.php new file mode 100644 index 0000000..ba063f8 --- /dev/null +++ b/app/Policies/NotificationTemplatePolicy.php @@ -0,0 +1,69 @@ +can('ViewAny:NotificationTemplate'); + } + + public function view(AuthUser $authUser, NotificationTemplate $notificationTemplate): bool + { + return $authUser->can('View:NotificationTemplate'); + } + + public function create(AuthUser $authUser): bool + { + return $authUser->can('Create:NotificationTemplate'); + } + + public function update(AuthUser $authUser, NotificationTemplate $notificationTemplate): bool + { + return $authUser->can('Update:NotificationTemplate'); + } + + public function delete(AuthUser $authUser, NotificationTemplate $notificationTemplate): bool + { + return $authUser->can('Delete:NotificationTemplate'); + } + + public function restore(AuthUser $authUser, NotificationTemplate $notificationTemplate): bool + { + return $authUser->can('Restore:NotificationTemplate'); + } + + public function forceDelete(AuthUser $authUser, NotificationTemplate $notificationTemplate): bool + { + return $authUser->can('ForceDelete:NotificationTemplate'); + } + + public function forceDeleteAny(AuthUser $authUser): bool + { + return $authUser->can('ForceDeleteAny:NotificationTemplate'); + } + + public function restoreAny(AuthUser $authUser): bool + { + return $authUser->can('RestoreAny:NotificationTemplate'); + } + + public function replicate(AuthUser $authUser, NotificationTemplate $notificationTemplate): bool + { + return $authUser->can('Replicate:NotificationTemplate'); + } + + public function reorder(AuthUser $authUser): bool + { + return $authUser->can('Reorder:NotificationTemplate'); + } +} diff --git a/app/Services/MemberService.php b/app/Services/MemberService.php index 85b7e94..11348cb 100644 --- a/app/Services/MemberService.php +++ b/app/Services/MemberService.php @@ -11,17 +11,15 @@ class MemberService { /** * Register a new member. - * @param array $data - * @return Member */ public function registerNewMember(array $data): Member { // Check if the member already exists $member = Member::where('email', $data['email'])->first(); - if (!$member) { + if (! $member) { // Create a new member - $member = new Member(); + $member = new Member; $member->status = 'pending'; $member->nature = 'physical'; $member->group_id = MemberGroup::where('identifier', 'website')->first()->id ?? null; @@ -50,10 +48,7 @@ class MemberService ]); - // Notify Admin - $admin = Member::where('role', 'admin')->first(); - event(new MemberRegistered($admin)); - + event(new MemberRegistered($member)); return $member; } diff --git a/database/factories/NotificationTemplateFactory.php b/database/factories/NotificationTemplateFactory.php new file mode 100644 index 0000000..e1a7120 --- /dev/null +++ b/database/factories/NotificationTemplateFactory.php @@ -0,0 +1,33 @@ + + */ +class NotificationTemplateFactory extends Factory +{ + protected $model = NotificationTemplate::class; + + public function definition(): array + { + return [ + 'identifier' => $this->faker->unique()->slug(2), + 'name' => $this->faker->sentence(3), + 'subject' => $this->faker->sentence(), + 'body' => $this->faker->paragraph(), + 'variables' => ['name' => 'Nom'], + 'is_active' => true, + ]; + } + + public function inactive(): static + { + return $this->state(fn (array $attributes) => [ + 'is_active' => false, + ]); + } +} diff --git a/database/migrations/2026_02_10_095945_create_notification_templates_table.php b/database/migrations/2026_02_10_095945_create_notification_templates_table.php new file mode 100644 index 0000000..b70cc35 --- /dev/null +++ b/database/migrations/2026_02_10_095945_create_notification_templates_table.php @@ -0,0 +1,34 @@ +id(); + $table->string('identifier')->unique(); + $table->string('name'); + $table->string('subject'); + $table->longText('body'); + $table->json('variables')->nullable(); + $table->boolean('is_active')->default(true); + $table->timestamps(); + $table->softDeletes(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::dropIfExists('notification_templates'); + } +}; diff --git a/database/seeders/DatabaseSeeder.php b/database/seeders/DatabaseSeeder.php index 32e79c7..c936107 100644 --- a/database/seeders/DatabaseSeeder.php +++ b/database/seeders/DatabaseSeeder.php @@ -3,10 +3,9 @@ namespace Database\Seeders; use App\Models\MemberGroup; +use App\Models\Package; use App\Models\Service; use App\Models\User; -use App\Models\Package; - // use Illuminate\Database\Console\Seeds\WithoutModelEvents; use Illuminate\Database\Seeder; @@ -36,11 +35,11 @@ class DatabaseSeeder extends Seeder ]); $websiteGroup = MemberGroup::updateOrCreate([ - 'name' => 'Site Web' + 'name' => 'Site Web', ], [ 'identifier' => 'website', - 'description' => 'Groupe d\'utilisateurs provenant du site web.' + 'description' => 'Groupe d\'utilisateurs provenant du site web.', ]); // Subscription packages @@ -49,20 +48,20 @@ class DatabaseSeeder extends Seeder 'identifier' => 'custom', 'name' => 'Sur-mesure', 'description' => 'Calcul du nombre de mois restant dans l\'année', - 'price' => '1.00' + 'price' => '1.00', ], [ 'identifier' => 'one-year', 'name' => 'Un an', 'description' => '12 mois à compter de la date de validation de l\'adhésion du membre', - 'price' => '12.00' + 'price' => '12.00', ], [ 'identifier' => 'two-years', 'name' => 'Deux ans', 'description' => '24 mois à compter de la date de validation de l\'adhésion du membre', - 'price' => '24.00' - ] + 'price' => '24.00', + ], ]; foreach ($packages as $package) { @@ -112,7 +111,7 @@ class DatabaseSeeder extends Seeder 'description' => 'Service d\'hébergement web', 'url' => '#', 'icon' => 'database', - ] + ], ]; foreach ($services as $service) { @@ -126,6 +125,9 @@ class DatabaseSeeder extends Seeder ]); } + // Notification templates + $this->call(NotificationTemplateSeeder::class); + // JaneDoe $userTest = User::updateOrCreate([ 'name' => 'JaneDoe', diff --git a/database/seeders/NotificationTemplateSeeder.php b/database/seeders/NotificationTemplateSeeder.php new file mode 100644 index 0000000..749420c --- /dev/null +++ b/database/seeders/NotificationTemplateSeeder.php @@ -0,0 +1,29 @@ + 'subscription_expired_phase1'], + [ + 'name' => 'Adhésion expirée - Phase 1', + 'subject' => 'Votre adhésion est expirée', + 'body' => '

Bonjour {member_name},

' + .'

Votre adhésion est arrivée à expiration le {expiry_date}.

' + .'

Pour continuer à profiter de nos services, merci de la renouveler.

' + .'

Merci pour votre confiance.

', + 'variables' => [ + 'member_name' => 'Nom complet du membre', + 'expiry_date' => 'Date de fin d\'adhésion', + ], + 'is_active' => true, + ] + ); + } +} diff --git a/lang/fr/notification_templates.php b/lang/fr/notification_templates.php new file mode 100644 index 0000000..0d8219d --- /dev/null +++ b/lang/fr/notification_templates.php @@ -0,0 +1,14 @@ + [ + 'singular_name' => 'Template Mail', + 'plural_name' => 'Templates Mail', + 'identifier' => 'Identifiant', + 'name' => 'Nom', + 'subject' => 'Objet', + 'body' => 'Contenu', + 'variables' => 'Variables disponibles', + 'is_active' => 'Actif', + ], +]; diff --git a/resources/js/actions/App/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.ts b/resources/js/actions/App/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.ts new file mode 100644 index 0000000..b993738 --- /dev/null +++ b/resources/js/actions/App/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.ts @@ -0,0 +1,83 @@ +import { queryParams, type RouteQueryOptions, type RouteDefinition, type RouteFormDefinition } from './../../../../../../wayfinder' +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\CreateNotificationTemplate::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.php:7 +* @route '/admin/notification-templates/create' +*/ +const CreateNotificationTemplate = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: CreateNotificationTemplate.url(options), + method: 'get', +}) + +CreateNotificationTemplate.definition = { + methods: ["get","head"], + url: '/admin/notification-templates/create', +} satisfies RouteDefinition<["get","head"]> + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\CreateNotificationTemplate::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.php:7 +* @route '/admin/notification-templates/create' +*/ +CreateNotificationTemplate.url = (options?: RouteQueryOptions) => { + return CreateNotificationTemplate.definition.url + queryParams(options) +} + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\CreateNotificationTemplate::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.php:7 +* @route '/admin/notification-templates/create' +*/ +CreateNotificationTemplate.get = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: CreateNotificationTemplate.url(options), + method: 'get', +}) + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\CreateNotificationTemplate::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.php:7 +* @route '/admin/notification-templates/create' +*/ +CreateNotificationTemplate.head = (options?: RouteQueryOptions): RouteDefinition<'head'> => ({ + url: CreateNotificationTemplate.url(options), + method: 'head', +}) + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\CreateNotificationTemplate::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.php:7 +* @route '/admin/notification-templates/create' +*/ +const CreateNotificationTemplateForm = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: CreateNotificationTemplate.url(options), + method: 'get', +}) + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\CreateNotificationTemplate::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.php:7 +* @route '/admin/notification-templates/create' +*/ +CreateNotificationTemplateForm.get = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: CreateNotificationTemplate.url(options), + method: 'get', +}) + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\CreateNotificationTemplate::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.php:7 +* @route '/admin/notification-templates/create' +*/ +CreateNotificationTemplateForm.head = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: CreateNotificationTemplate.url({ + [options?.mergeQuery ? 'mergeQuery' : 'query']: { + _method: 'HEAD', + ...(options?.query ?? options?.mergeQuery ?? {}), + } + }), + method: 'get', +}) + +CreateNotificationTemplate.form = CreateNotificationTemplateForm + +export default CreateNotificationTemplate \ No newline at end of file diff --git a/resources/js/actions/App/Filament/Resources/NotificationTemplates/Pages/EditNotificationTemplate.ts b/resources/js/actions/App/Filament/Resources/NotificationTemplates/Pages/EditNotificationTemplate.ts new file mode 100644 index 0000000..e111102 --- /dev/null +++ b/resources/js/actions/App/Filament/Resources/NotificationTemplates/Pages/EditNotificationTemplate.ts @@ -0,0 +1,101 @@ +import { queryParams, type RouteQueryOptions, type RouteDefinition, type RouteFormDefinition, applyUrlDefaults } from './../../../../../../wayfinder' +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\EditNotificationTemplate::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/EditNotificationTemplate.php:7 +* @route '/admin/notification-templates/{record}/edit' +*/ +const EditNotificationTemplate = (args: { record: string | number } | [record: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: EditNotificationTemplate.url(args, options), + method: 'get', +}) + +EditNotificationTemplate.definition = { + methods: ["get","head"], + url: '/admin/notification-templates/{record}/edit', +} satisfies RouteDefinition<["get","head"]> + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\EditNotificationTemplate::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/EditNotificationTemplate.php:7 +* @route '/admin/notification-templates/{record}/edit' +*/ +EditNotificationTemplate.url = (args: { record: string | number } | [record: string | number ] | string | number, options?: RouteQueryOptions) => { + if (typeof args === 'string' || typeof args === 'number') { + args = { record: args } + } + + if (Array.isArray(args)) { + args = { + record: args[0], + } + } + + args = applyUrlDefaults(args) + + const parsedArgs = { + record: args.record, + } + + return EditNotificationTemplate.definition.url + .replace('{record}', parsedArgs.record.toString()) + .replace(/\/+$/, '') + queryParams(options) +} + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\EditNotificationTemplate::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/EditNotificationTemplate.php:7 +* @route '/admin/notification-templates/{record}/edit' +*/ +EditNotificationTemplate.get = (args: { record: string | number } | [record: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: EditNotificationTemplate.url(args, options), + method: 'get', +}) + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\EditNotificationTemplate::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/EditNotificationTemplate.php:7 +* @route '/admin/notification-templates/{record}/edit' +*/ +EditNotificationTemplate.head = (args: { record: string | number } | [record: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'head'> => ({ + url: EditNotificationTemplate.url(args, options), + method: 'head', +}) + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\EditNotificationTemplate::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/EditNotificationTemplate.php:7 +* @route '/admin/notification-templates/{record}/edit' +*/ +const EditNotificationTemplateForm = (args: { record: string | number } | [record: string | number ] | string | number, options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: EditNotificationTemplate.url(args, options), + method: 'get', +}) + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\EditNotificationTemplate::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/EditNotificationTemplate.php:7 +* @route '/admin/notification-templates/{record}/edit' +*/ +EditNotificationTemplateForm.get = (args: { record: string | number } | [record: string | number ] | string | number, options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: EditNotificationTemplate.url(args, options), + method: 'get', +}) + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\EditNotificationTemplate::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/EditNotificationTemplate.php:7 +* @route '/admin/notification-templates/{record}/edit' +*/ +EditNotificationTemplateForm.head = (args: { record: string | number } | [record: string | number ] | string | number, options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: EditNotificationTemplate.url(args, { + [options?.mergeQuery ? 'mergeQuery' : 'query']: { + _method: 'HEAD', + ...(options?.query ?? options?.mergeQuery ?? {}), + } + }), + method: 'get', +}) + +EditNotificationTemplate.form = EditNotificationTemplateForm + +export default EditNotificationTemplate \ No newline at end of file diff --git a/resources/js/actions/App/Filament/Resources/NotificationTemplates/Pages/ListNotificationTemplates.ts b/resources/js/actions/App/Filament/Resources/NotificationTemplates/Pages/ListNotificationTemplates.ts new file mode 100644 index 0000000..372f9ae --- /dev/null +++ b/resources/js/actions/App/Filament/Resources/NotificationTemplates/Pages/ListNotificationTemplates.ts @@ -0,0 +1,83 @@ +import { queryParams, type RouteQueryOptions, type RouteDefinition, type RouteFormDefinition } from './../../../../../../wayfinder' +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\ListNotificationTemplates::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/ListNotificationTemplates.php:7 +* @route '/admin/notification-templates' +*/ +const ListNotificationTemplates = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: ListNotificationTemplates.url(options), + method: 'get', +}) + +ListNotificationTemplates.definition = { + methods: ["get","head"], + url: '/admin/notification-templates', +} satisfies RouteDefinition<["get","head"]> + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\ListNotificationTemplates::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/ListNotificationTemplates.php:7 +* @route '/admin/notification-templates' +*/ +ListNotificationTemplates.url = (options?: RouteQueryOptions) => { + return ListNotificationTemplates.definition.url + queryParams(options) +} + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\ListNotificationTemplates::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/ListNotificationTemplates.php:7 +* @route '/admin/notification-templates' +*/ +ListNotificationTemplates.get = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: ListNotificationTemplates.url(options), + method: 'get', +}) + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\ListNotificationTemplates::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/ListNotificationTemplates.php:7 +* @route '/admin/notification-templates' +*/ +ListNotificationTemplates.head = (options?: RouteQueryOptions): RouteDefinition<'head'> => ({ + url: ListNotificationTemplates.url(options), + method: 'head', +}) + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\ListNotificationTemplates::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/ListNotificationTemplates.php:7 +* @route '/admin/notification-templates' +*/ +const ListNotificationTemplatesForm = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: ListNotificationTemplates.url(options), + method: 'get', +}) + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\ListNotificationTemplates::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/ListNotificationTemplates.php:7 +* @route '/admin/notification-templates' +*/ +ListNotificationTemplatesForm.get = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: ListNotificationTemplates.url(options), + method: 'get', +}) + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\ListNotificationTemplates::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/ListNotificationTemplates.php:7 +* @route '/admin/notification-templates' +*/ +ListNotificationTemplatesForm.head = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: ListNotificationTemplates.url({ + [options?.mergeQuery ? 'mergeQuery' : 'query']: { + _method: 'HEAD', + ...(options?.query ?? options?.mergeQuery ?? {}), + } + }), + method: 'get', +}) + +ListNotificationTemplates.form = ListNotificationTemplatesForm + +export default ListNotificationTemplates \ No newline at end of file diff --git a/resources/js/actions/App/Filament/Resources/NotificationTemplates/Pages/index.ts b/resources/js/actions/App/Filament/Resources/NotificationTemplates/Pages/index.ts new file mode 100644 index 0000000..059efba --- /dev/null +++ b/resources/js/actions/App/Filament/Resources/NotificationTemplates/Pages/index.ts @@ -0,0 +1,11 @@ +import ListNotificationTemplates from './ListNotificationTemplates' +import CreateNotificationTemplate from './CreateNotificationTemplate' +import EditNotificationTemplate from './EditNotificationTemplate' + +const Pages = { + ListNotificationTemplates: Object.assign(ListNotificationTemplates, ListNotificationTemplates), + CreateNotificationTemplate: Object.assign(CreateNotificationTemplate, CreateNotificationTemplate), + EditNotificationTemplate: Object.assign(EditNotificationTemplate, EditNotificationTemplate), +} + +export default Pages \ No newline at end of file diff --git a/resources/js/actions/App/Filament/Resources/NotificationTemplates/index.ts b/resources/js/actions/App/Filament/Resources/NotificationTemplates/index.ts new file mode 100644 index 0000000..de563df --- /dev/null +++ b/resources/js/actions/App/Filament/Resources/NotificationTemplates/index.ts @@ -0,0 +1,7 @@ +import Pages from './Pages' + +const NotificationTemplates = { + Pages: Object.assign(Pages, Pages), +} + +export default NotificationTemplates \ No newline at end of file diff --git a/resources/js/actions/App/Filament/Resources/index.ts b/resources/js/actions/App/Filament/Resources/index.ts index 0e61502..9d4f161 100644 --- a/resources/js/actions/App/Filament/Resources/index.ts +++ b/resources/js/actions/App/Filament/Resources/index.ts @@ -1,6 +1,7 @@ import MemberGroups from './MemberGroups' import Members from './Members' import Memberships from './Memberships' +import NotificationTemplates from './NotificationTemplates' import Packages from './Packages' import Services from './Services' import Users from './Users' @@ -9,6 +10,7 @@ const Resources = { MemberGroups: Object.assign(MemberGroups, MemberGroups), Members: Object.assign(Members, Members), Memberships: Object.assign(Memberships, Memberships), + NotificationTemplates: Object.assign(NotificationTemplates, NotificationTemplates), Packages: Object.assign(Packages, Packages), Services: Object.assign(Services, Services), Users: Object.assign(Users, Users), diff --git a/resources/js/routes/boost/index.ts b/resources/js/routes/boost/index.ts new file mode 100644 index 0000000..0c47fbd --- /dev/null +++ b/resources/js/routes/boost/index.ts @@ -0,0 +1,57 @@ +import { queryParams, type RouteQueryOptions, type RouteDefinition, type RouteFormDefinition } from './../../wayfinder' +/** +* @see vendor/laravel/boost/src/BoostServiceProvider.php:117 +* @route '/_boost/browser-logs' +*/ +export const browserLogs = (options?: RouteQueryOptions): RouteDefinition<'post'> => ({ + url: browserLogs.url(options), + method: 'post', +}) + +browserLogs.definition = { + methods: ["post"], + url: '/_boost/browser-logs', +} satisfies RouteDefinition<["post"]> + +/** +* @see vendor/laravel/boost/src/BoostServiceProvider.php:117 +* @route '/_boost/browser-logs' +*/ +browserLogs.url = (options?: RouteQueryOptions) => { + return browserLogs.definition.url + queryParams(options) +} + +/** +* @see vendor/laravel/boost/src/BoostServiceProvider.php:117 +* @route '/_boost/browser-logs' +*/ +browserLogs.post = (options?: RouteQueryOptions): RouteDefinition<'post'> => ({ + url: browserLogs.url(options), + method: 'post', +}) + +/** +* @see vendor/laravel/boost/src/BoostServiceProvider.php:117 +* @route '/_boost/browser-logs' +*/ +const browserLogsForm = (options?: RouteQueryOptions): RouteFormDefinition<'post'> => ({ + action: browserLogs.url(options), + method: 'post', +}) + +/** +* @see vendor/laravel/boost/src/BoostServiceProvider.php:117 +* @route '/_boost/browser-logs' +*/ +browserLogsForm.post = (options?: RouteQueryOptions): RouteFormDefinition<'post'> => ({ + action: browserLogs.url(options), + method: 'post', +}) + +browserLogs.form = browserLogsForm + +const boost = { + browserLogs: Object.assign(browserLogs, browserLogs), +} + +export default boost \ No newline at end of file diff --git a/resources/js/routes/filament/admin/resources/index.ts b/resources/js/routes/filament/admin/resources/index.ts index ed89add..7de3445 100644 --- a/resources/js/routes/filament/admin/resources/index.ts +++ b/resources/js/routes/filament/admin/resources/index.ts @@ -1,6 +1,7 @@ import memberGroups from './member-groups' import members from './members' import memberships from './memberships' +import notificationTemplates from './notification-templates' import packages from './packages' import services from './services' import users from './users' @@ -10,6 +11,7 @@ const resources = { memberGroups: Object.assign(memberGroups, memberGroups), members: Object.assign(members, members), memberships: Object.assign(memberships, memberships), + notificationTemplates: Object.assign(notificationTemplates, notificationTemplates), packages: Object.assign(packages, packages), services: Object.assign(services, services), users: Object.assign(users, users), diff --git a/resources/js/routes/filament/admin/resources/notification-templates/index.ts b/resources/js/routes/filament/admin/resources/notification-templates/index.ts new file mode 100644 index 0000000..2ad9611 --- /dev/null +++ b/resources/js/routes/filament/admin/resources/notification-templates/index.ts @@ -0,0 +1,269 @@ +import { queryParams, type RouteQueryOptions, type RouteDefinition, type RouteFormDefinition, applyUrlDefaults } from './../../../../../wayfinder' +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\ListNotificationTemplates::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/ListNotificationTemplates.php:7 +* @route '/admin/notification-templates' +*/ +export const index = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: index.url(options), + method: 'get', +}) + +index.definition = { + methods: ["get","head"], + url: '/admin/notification-templates', +} satisfies RouteDefinition<["get","head"]> + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\ListNotificationTemplates::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/ListNotificationTemplates.php:7 +* @route '/admin/notification-templates' +*/ +index.url = (options?: RouteQueryOptions) => { + return index.definition.url + queryParams(options) +} + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\ListNotificationTemplates::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/ListNotificationTemplates.php:7 +* @route '/admin/notification-templates' +*/ +index.get = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: index.url(options), + method: 'get', +}) + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\ListNotificationTemplates::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/ListNotificationTemplates.php:7 +* @route '/admin/notification-templates' +*/ +index.head = (options?: RouteQueryOptions): RouteDefinition<'head'> => ({ + url: index.url(options), + method: 'head', +}) + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\ListNotificationTemplates::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/ListNotificationTemplates.php:7 +* @route '/admin/notification-templates' +*/ +const indexForm = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: index.url(options), + method: 'get', +}) + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\ListNotificationTemplates::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/ListNotificationTemplates.php:7 +* @route '/admin/notification-templates' +*/ +indexForm.get = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: index.url(options), + method: 'get', +}) + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\ListNotificationTemplates::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/ListNotificationTemplates.php:7 +* @route '/admin/notification-templates' +*/ +indexForm.head = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: index.url({ + [options?.mergeQuery ? 'mergeQuery' : 'query']: { + _method: 'HEAD', + ...(options?.query ?? options?.mergeQuery ?? {}), + } + }), + method: 'get', +}) + +index.form = indexForm + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\CreateNotificationTemplate::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.php:7 +* @route '/admin/notification-templates/create' +*/ +export const create = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: create.url(options), + method: 'get', +}) + +create.definition = { + methods: ["get","head"], + url: '/admin/notification-templates/create', +} satisfies RouteDefinition<["get","head"]> + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\CreateNotificationTemplate::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.php:7 +* @route '/admin/notification-templates/create' +*/ +create.url = (options?: RouteQueryOptions) => { + return create.definition.url + queryParams(options) +} + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\CreateNotificationTemplate::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.php:7 +* @route '/admin/notification-templates/create' +*/ +create.get = (options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: create.url(options), + method: 'get', +}) + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\CreateNotificationTemplate::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.php:7 +* @route '/admin/notification-templates/create' +*/ +create.head = (options?: RouteQueryOptions): RouteDefinition<'head'> => ({ + url: create.url(options), + method: 'head', +}) + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\CreateNotificationTemplate::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.php:7 +* @route '/admin/notification-templates/create' +*/ +const createForm = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: create.url(options), + method: 'get', +}) + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\CreateNotificationTemplate::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.php:7 +* @route '/admin/notification-templates/create' +*/ +createForm.get = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: create.url(options), + method: 'get', +}) + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\CreateNotificationTemplate::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/CreateNotificationTemplate.php:7 +* @route '/admin/notification-templates/create' +*/ +createForm.head = (options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: create.url({ + [options?.mergeQuery ? 'mergeQuery' : 'query']: { + _method: 'HEAD', + ...(options?.query ?? options?.mergeQuery ?? {}), + } + }), + method: 'get', +}) + +create.form = createForm + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\EditNotificationTemplate::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/EditNotificationTemplate.php:7 +* @route '/admin/notification-templates/{record}/edit' +*/ +export const edit = (args: { record: string | number } | [record: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: edit.url(args, options), + method: 'get', +}) + +edit.definition = { + methods: ["get","head"], + url: '/admin/notification-templates/{record}/edit', +} satisfies RouteDefinition<["get","head"]> + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\EditNotificationTemplate::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/EditNotificationTemplate.php:7 +* @route '/admin/notification-templates/{record}/edit' +*/ +edit.url = (args: { record: string | number } | [record: string | number ] | string | number, options?: RouteQueryOptions) => { + if (typeof args === 'string' || typeof args === 'number') { + args = { record: args } + } + + if (Array.isArray(args)) { + args = { + record: args[0], + } + } + + args = applyUrlDefaults(args) + + const parsedArgs = { + record: args.record, + } + + return edit.definition.url + .replace('{record}', parsedArgs.record.toString()) + .replace(/\/+$/, '') + queryParams(options) +} + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\EditNotificationTemplate::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/EditNotificationTemplate.php:7 +* @route '/admin/notification-templates/{record}/edit' +*/ +edit.get = (args: { record: string | number } | [record: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'get'> => ({ + url: edit.url(args, options), + method: 'get', +}) + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\EditNotificationTemplate::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/EditNotificationTemplate.php:7 +* @route '/admin/notification-templates/{record}/edit' +*/ +edit.head = (args: { record: string | number } | [record: string | number ] | string | number, options?: RouteQueryOptions): RouteDefinition<'head'> => ({ + url: edit.url(args, options), + method: 'head', +}) + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\EditNotificationTemplate::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/EditNotificationTemplate.php:7 +* @route '/admin/notification-templates/{record}/edit' +*/ +const editForm = (args: { record: string | number } | [record: string | number ] | string | number, options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: edit.url(args, options), + method: 'get', +}) + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\EditNotificationTemplate::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/EditNotificationTemplate.php:7 +* @route '/admin/notification-templates/{record}/edit' +*/ +editForm.get = (args: { record: string | number } | [record: string | number ] | string | number, options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: edit.url(args, options), + method: 'get', +}) + +/** +* @see \App\Filament\Resources\NotificationTemplates\Pages\EditNotificationTemplate::__invoke +* @see app/Filament/Resources/NotificationTemplates/Pages/EditNotificationTemplate.php:7 +* @route '/admin/notification-templates/{record}/edit' +*/ +editForm.head = (args: { record: string | number } | [record: string | number ] | string | number, options?: RouteQueryOptions): RouteFormDefinition<'get'> => ({ + action: edit.url(args, { + [options?.mergeQuery ? 'mergeQuery' : 'query']: { + _method: 'HEAD', + ...(options?.query ?? options?.mergeQuery ?? {}), + } + }), + method: 'get', +}) + +edit.form = editForm + +const notificationTemplates = { + index: Object.assign(index, index), + create: Object.assign(create, create), + edit: Object.assign(edit, edit), +} + +export default notificationTemplates \ No newline at end of file diff --git a/resources/views/notifications/mail-template.blade.php b/resources/views/notifications/mail-template.blade.php new file mode 100644 index 0000000..2670393 --- /dev/null +++ b/resources/views/notifications/mail-template.blade.php @@ -0,0 +1,3 @@ + +{!! $body !!} + diff --git a/tests/Feature/NotificationTemplateTest.php b/tests/Feature/NotificationTemplateTest.php new file mode 100644 index 0000000..d79959e --- /dev/null +++ b/tests/Feature/NotificationTemplateTest.php @@ -0,0 +1,77 @@ +create([ + 'identifier' => 'test_template', + 'is_active' => true, + ]); + + $found = NotificationTemplate::findByIdentifier('test_template'); + + $this->assertNotNull($found); + $this->assertEquals($template->id, $found->id); + } + + public function test_find_by_identifier_returns_null_for_inactive_template(): void + { + NotificationTemplate::factory()->inactive()->create([ + 'identifier' => 'inactive_template', + ]); + + $found = NotificationTemplate::findByIdentifier('inactive_template'); + + $this->assertNull($found); + } + + public function test_render_subject_replaces_placeholders(): void + { + $template = NotificationTemplate::factory()->create([ + 'subject' => 'Bonjour {member_name}, votre adhésion expire le {expiry_date}', + ]); + + $result = $template->renderSubject([ + 'member_name' => 'Jean Dupont', + 'expiry_date' => '2026-01-31', + ]); + + $this->assertEquals('Bonjour Jean Dupont, votre adhésion expire le 2026-01-31', $result); + } + + public function test_render_body_replaces_placeholders(): void + { + $template = NotificationTemplate::factory()->create([ + 'body' => '

Bonjour {member_name}

Expiration : {expiry_date}

', + ]); + + $result = $template->renderBody([ + 'member_name' => 'Marie Martin', + 'expiry_date' => '2026-06-15', + ]); + + $this->assertEquals('

Bonjour Marie Martin

Expiration : 2026-06-15

', $result); + } + + public function test_missing_placeholder_is_left_intact(): void + { + $template = NotificationTemplate::factory()->create([ + 'subject' => 'Bonjour {member_name}, date : {expiry_date}', + ]); + + $result = $template->renderSubject([ + 'member_name' => 'Jean Dupont', + ]); + + $this->assertEquals('Bonjour Jean Dupont, date : {expiry_date}', $result); + } +} diff --git a/tests/Feature/SendSubscriptionExpiredPhase1NotificationsTest.php b/tests/Feature/SendSubscriptionExpiredPhase1NotificationsTest.php new file mode 100644 index 0000000..e1d7fc0 --- /dev/null +++ b/tests/Feature/SendSubscriptionExpiredPhase1NotificationsTest.php @@ -0,0 +1,88 @@ + 'test-package', + 'name' => 'Test Package', + 'is_active' => true, + ]); + + $member = Member::create([ + 'email' => fake()->unique()->safeEmail(), + 'firstname' => fake()->firstName(), + 'lastname' => fake()->lastName(), + 'status' => 'valid', + 'nature' => 'physical', + ]); + + Membership::create([ + 'member_id' => $member->id, + 'package_id' => $package->id, + 'status' => 'expired', + 'end_date' => '2025-12-31', + 'amount' => 12.00, + 'payment_status' => 'paid', + ]); + + return $member; + } + + public function test_job_sends_notifications_to_expired_members(): void + { + Notification::fake(); + + NotificationTemplate::factory()->create([ + 'identifier' => 'subscription_expired_phase1', + 'is_active' => true, + ]); + + $expiredMember = $this->createMemberWithExpiredMembership(); + + (new SendSubscriptionExpiredPhase1Notifications)->handle(); + + Notification::assertSentTo($expiredMember, SubscriptionExpiredPhase1::class); + } + + public function test_job_does_nothing_when_template_is_inactive(): void + { + Notification::fake(); + + NotificationTemplate::factory()->inactive()->create([ + 'identifier' => 'subscription_expired_phase1', + ]); + + $this->createMemberWithExpiredMembership(); + + (new SendSubscriptionExpiredPhase1Notifications)->handle(); + + Notification::assertNothingSent(); + } + + public function test_job_does_nothing_when_template_does_not_exist(): void + { + Notification::fake(); + + $this->createMemberWithExpiredMembership(); + + (new SendSubscriptionExpiredPhase1Notifications)->handle(); + + Notification::assertNothingSent(); + } +}