Files
projectsend/app/Modules/Platform/Onboarding/QuickStart.php
T
ignacionelson 4ce6793da9 Show a new installation's administrator around, once
Setup ended by handing somebody a login form and an empty dashboard.
Everything this application can do was one menu away, and which menu was
theirs to discover.

The first time the administrator signs in to a new installation they now
land on a short ordered list of what is worth doing first — add a client,
upload a file, group the people who get the same things, choose how the
file lists and the email look, point it at a mail server, add the team,
check the scheduler — each a link straight to the screen that does it.

The list is filtered twice, and both filters matter. By permission,
because a link that answers 403 is worse than no link. And by edition:
a managed installation is not sent off to configure a mail server
somebody else runs, to create staff accounts that are not its to create,
or to check a scheduler it does not host. Those three drop out on Cloud
and the other five remain.

Two steps tick themselves, because the database can answer them: a client
exists, a file exists. Nothing else is checkable without guessing — a
theme that was never changed looks exactly like one chosen deliberately —
and a tick meaning "we assume so" is worse than no tick.

The invitation to the Discord is at the very bottom, after the list.
Somebody who has just installed this came with a job in mind, and opening
with a social invitation is the fastest way to lose them.

The marker is raised where a first administrator comes into existence —
the setup screen and `projectsend:admin`, so a container provisioned from
environment variables is welcomed too — and it is false by default, so an
installation that updates into this feature is not congratulated on an
install it finished a year ago.

RedirectToWhatsNew becomes RedirectToGreeting and answers for both: they
are the same interruption, and a second middleware on the same route
would have to know about the first to avoid arguing with it. Installing
wins; release notes for a version you never ran are the wrong greeting.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-15 19:44:22 -03:00

148 lines
6.3 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Modules\Platform\Onboarding;
use App\Models\User;
use App\Modules\Files\Models\File;
use App\Modules\Identity\Permissions\Permission;
use App\Modules\Identity\Permissions\PermissionChecker;
use App\Modules\Identity\UserType;
use App\Modules\Platform\Capabilities\Capability;
use App\Modules\Platform\Capabilities\CapabilityRegistry;
/**
* The short list of things worth doing on a brand-new installation, in
* the order they make sense, filtered to what this person can actually
* do here.
*
* Two filters, and both matter. **Permission** keeps the list honest for
* anybody who is not an administrator — a link to a screen that answers
* 403 is worse than no link. **Capability** keeps it honest per edition:
* on a managed installation there are no staff accounts to create, no
* mail server to point at and no scheduler to check, because somebody
* else does all three. A getting-started list that opens with three tasks
* you are not allowed to perform teaches the reader to ignore it.
*
* The two tasks that can be answered from the database are answered:
* "create your first client" and "upload a file" tick themselves. Nothing
* else is checkable without guessing — a theme that was never changed is
* indistinguishable from one that was chosen deliberately — and a tick
* that means "we assume so" is worse than no tick at all.
*/
class QuickStart
{
public function __construct(
private readonly CapabilityRegistry $capabilities,
private readonly PermissionChecker $permissions,
) {}
/**
* @return list<array{key: string, title: string, description: string, href: string, done: bool}>
*/
public function forUser(User $user): array
{
$items = [];
if ($this->permissions->allows($user, Permission::CreateClients)) {
$items[] = [
'key' => 'client',
'title' => __('Add your first client'),
'description' => __('A client is somebody you send files to. They get their own account and see only what you share with them.'),
'href' => route('clients.create', absolute: false),
'done' => $this->hasAClient(),
];
}
if ($this->permissions->allows($user, Permission::Upload)) {
$items[] = [
'key' => 'upload',
'title' => __('Upload a file'),
'description' => __('Drop a file in and choose who it goes to. Uploads resume by themselves if the connection drops.'),
'href' => route('files.create', absolute: false),
'done' => $this->hasAFile(),
];
}
if ($this->permissions->allows($user, Permission::CreateGroups)) {
$items[] = [
'key' => 'group',
'title' => __('Group the clients who get the same things'),
'description' => __('Share with a group once instead of with six people individually, and anyone added later gets it too.'),
'href' => route('groups.create', absolute: false),
'done' => false,
];
}
if ($this->permissions->allows($user, Permission::EditSettings)) {
$items[] = [
'key' => 'theme',
'title' => __('Choose how your file lists look'),
'description' => __('Four layouts for the pages your clients and visitors see. Each one previews before you switch.'),
'href' => route('system-settings.theming.edit', absolute: false),
'done' => false,
];
$items[] = [
'key' => 'email-theme',
'title' => __('Choose how your email looks'),
'description' => __('Four themes for the messages ProjectSend sends, previewed on a real message rather than a mock-up.'),
'href' => route('system-settings.theming.edit', ['tab' => 'email'], absolute: false),
'done' => false,
];
}
// Community only: on a managed installation the mail server is
// ours, and there is nothing here to point anywhere.
if ($this->permissions->allows($user, Permission::EditSettings)
&& $this->capabilities->has(Capability::EmailTransportConfigure)) {
$items[] = [
'key' => 'email',
'title' => __('Point ProjectSend at your mail server'),
'description' => __('Notifications, password resets and share links all arrive by email, so this is worth doing before your first client does.'),
'href' => route('system-settings.email.edit', absolute: false),
'done' => false,
];
}
// Community only, and the example the brief named: a managed
// installation has no staff accounts of its own to hand out.
if ($this->permissions->allows($user, Permission::CreateUsers)
&& $this->capabilities->has(Capability::UsersManage)) {
$items[] = [
'key' => 'team',
'title' => __('Add the rest of your team'),
'description' => __('Staff accounts with roles, so people get exactly the part of this they need and nothing else.'),
'href' => route('users.create', absolute: false),
'done' => false,
];
}
// Community only: scheduled work is somebody else's problem on a
// managed installation, and its screen does not exist there.
if ($this->permissions->allows($user, Permission::ViewSystemInfo)
&& $this->capabilities->has(Capability::SchedulerMonitoring)) {
$items[] = [
'key' => 'scheduler',
'title' => __('Check the scheduler is running'),
'description' => __('Expiring files, cleanups and queued email all depend on it. This screen tells you whether it has run.'),
'href' => route('system-settings.scheduler.index', absolute: false),
'done' => false,
];
}
return $items;
}
private function hasAClient(): bool
{
return User::query()->where('type', UserType::Client)->exists();
}
private function hasAFile(): bool
{
return File::query()->exists();
}
}