mirror of
https://github.com/projectsend/projectsend.git
synced 2026-09-21 11:03:19 +00:00
f06a3c7ab3
Invitations produced no in-app notification at all, and neither did self-registration: the whole Clients module raised none. The only admin-facing signal when an account appeared was an email to whatever raw addresses an operator typed into a setting -- addresses that need not correspond to any account in this installation, and that plenty of installations never fill in. An invitation could be accepted and nobody signed in would ever be told. So: one new type, client_registered, reaching the bell and /notifications. One type for both doors on purpose. A client arriving through the public form and one arriving through an invitation are the same event to the person being told -- an account now exists that did not -- and a second type would buy nothing, because preferences here govern email only, so it could not have been switched off separately anyway. Which door it came through is one click away in the activity log and on the invitations screen. In-app only, the reasoning client_uploaded already states: email for this event is sent separately to that address list, and routing it through Notifier's mail dispatch too would risk double-emailing any staff member who is also on it. Two things worth stating about who gets it. Recipients are resolved at the call site, because Notifier authorizes nothing by design -- its security contract is explicit that a broad query must never be handed to it. And a client-scoped staff member is deliberately not told: their whole view is the clients assigned to them, and a brand-new account is assigned to nobody, so it would link them to a screen they are refused. Which is also why the notification links to the clients list filtered to the address, and not to clients.edit: that route is gated by edit_clients while these recipients are chosen by manage_clients. A notification that refuses the person it was sent to is worse than one that lands a click short. Translated in all sixteen locales, and the redemption was driven through a real browser to see the row arrive. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CPk8qAs38pudYGWwmGkYPe
41 lines
1.9 KiB
PHP
41 lines
1.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Modules\Clients;
|
|
|
|
use App\Modules\Notifications\NotificationTypeDefinition;
|
|
use App\Modules\Notifications\NotificationTypeRegistry;
|
|
use Illuminate\Support\ServiceProvider;
|
|
|
|
class ClientsServiceProvider extends ServiceProvider
|
|
{
|
|
public function boot(): void
|
|
{
|
|
// In-app only, the same reasoning client_uploaded gives: email for
|
|
// this event is already sent separately, to whatever raw addresses
|
|
// Setting::AdminNotificationEmails lists, via
|
|
// AdminClientRegisteredNotification. Routing it through Notifier's
|
|
// mail dispatch as well would risk double-emailing any staff member
|
|
// who also appears in that list.
|
|
//
|
|
// One type for both doors, deliberately. A client arriving through
|
|
// the public form and one arriving through an invitation are the
|
|
// same event to the person being told — an account now exists that
|
|
// did not — and a second type would buy nothing: preferences here
|
|
// govern email only (see NotificationPreferences), so it could not
|
|
// be switched off separately, and which door it came through is one
|
|
// click away in the activity log and on the invitations screen.
|
|
$this->app->make(NotificationTypeRegistry::class)->register(new NotificationTypeDefinition(
|
|
key: 'client_registered',
|
|
label: 'A new client registered an account',
|
|
template: ':clientName (:clientEmail) registered a client account',
|
|
// The list, filtered to this address — not clients.edit, which
|
|
// is gated by edit_clients while the recipients below are chosen
|
|
// by manage_clients. A notification that refuses the person it
|
|
// was sent to is worse than one that lands a click short.
|
|
url: fn (array $data): string => route('clients.index', ['search' => $data['clientEmail']]),
|
|
));
|
|
}
|
|
}
|