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 */ 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 */ 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 */ 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 $socialLogins * @return array */ 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 */ 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); } }