Files
2026-10-09 08:21:06 +02:00

143 lines
4.7 KiB
PHP

<?php
namespace App\Support;
use App\Models\User;
use Illuminate\Support\Collection;
/**
* Serialises users for the Inertia pages.
*
* The presenter derived fields (avatar, formatted birthday, humanised last
* login) used to be read straight off `$user->present()` inside Blade. The Vue
* pages receive them as plain data instead, so the shapes live here rather than
* being spread across controllers.
*/
class UserPayload
{
/**
* A row of the users table.
*
* @return array<string, mixed>
*/
public static function row(User $user): array
{
return [
'id' => $user->id,
'username' => $user->username,
'fullName' => trim("{$user->first_name} {$user->last_name}"),
'email' => $user->email,
'avatar' => $user->present()->avatar,
'nameOrEmail' => $user->present()->nameOrEmail,
'registeredAt' => $user->created_at->format(config('app.date_format')),
'status' => $user->status->value,
'statusLabel' => trans("app.status.{$user->status->value}"),
'statusVariant' => $user->present()->labelClass,
'waitingApproval' => $user->isWaitingApproval(),
'canImpersonate' => self::canImpersonate($user),
];
}
/**
* The read only detail card.
*
* @return array<string, mixed>
*/
public static function details(User $user): array
{
return [
'id' => $user->id,
'name' => $user->present()->name,
'nameOrEmail' => $user->present()->nameOrEmail,
'email' => $user->email,
'avatar' => $user->present()->avatar,
'phone' => $user->phone,
'birthday' => $user->present()->birthday,
'fullAddress' => $user->present()->fullAddress,
'lastLogin' => $user->present()->lastLogin,
];
}
/**
* The editable fields behind the details and login details forms.
*
* @return array<string, mixed>
*/
public static function form(User $user): array
{
return [
'id' => $user->id,
'nameOrEmail' => $user->present()->nameOrEmail,
'avatar' => $user->present()->avatar,
'first_name' => $user->first_name,
'last_name' => $user->last_name,
// A native date input round trips ISO, and `nullable|date` accepts it.
'birthday' => $user->birthday?->format('Y-m-d'),
'phone' => $user->phone,
'address' => $user->address,
'country_id' => $user->country_id ? (int) $user->country_id : 0,
'role_id' => (int) $user->role_id,
'status' => $user->status->value,
'email' => $user->email,
'username' => $user->username,
'force_password_change' => (bool) $user->force_password_change,
'waitingApproval' => $user->isWaitingApproval(),
];
}
/**
* Every image the avatar picker can offer: the placeholder, Gravatar, and
* one per connected social account.
*
* @param Collection<int, object> $socialLogins
* @return array<int, array{label: string, url: string, external: bool}>
*/
public static function avatarSources(User $user, Collection $socialLogins): array
{
$sources = [
['label' => __('No Photo'), 'url' => url('assets/img/profile.png'), 'external' => true],
['label' => __('Gravatar'), 'url' => $user->gravatar(), 'external' => true],
];
foreach ($socialLogins as $login) {
$sources[] = [
'label' => ucfirst($login->provider),
'url' => $login->avatar,
'external' => true,
];
}
return $sources;
}
/**
* Two-factor state, mirroring the branching the Blade partial did inline.
*
* @return array<string, mixed>
*/
public static function twoFactor(User $user): array
{
$needsVerification = session('tab') === '2fa' && $user->needsTwoFactorVerification();
return [
'enabled' => $user->twoFactorEnabled(),
'needsVerification' => $needsVerification,
'action' => match (true) {
$needsVerification => 'verify',
$user->twoFactorEnabled() => 'disable',
default => 'enable',
},
'qrCode' => $needsVerification ? $user->twoFactorQrCodeSvg() : null,
];
}
/**
* Exactly what the @canBeImpersonated Blade directive compiled to, so the
* link appears in the same cases it always did.
*/
public static function canImpersonate(User $user): bool
{
return can_be_impersonated($user);
}
}