Add profile avatar uploads
This commit is contained in:
@@ -2,6 +2,7 @@
|
||||
|
||||
namespace App\Actions\Fortify;
|
||||
|
||||
use App\Actions\Users\UpdateUserAvatar;
|
||||
use App\Concerns\PasswordValidationRules;
|
||||
use App\Concerns\ProfileValidationRules;
|
||||
use App\Models\User;
|
||||
@@ -12,6 +13,10 @@ class CreateNewUser implements CreatesNewUsers
|
||||
{
|
||||
use PasswordValidationRules, ProfileValidationRules;
|
||||
|
||||
public function __construct(
|
||||
private readonly UpdateUserAvatar $avatars,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Validate and create a newly registered user.
|
||||
*
|
||||
@@ -24,10 +29,12 @@ class CreateNewUser implements CreatesNewUsers
|
||||
'password' => $this->passwordRules(),
|
||||
])->validate();
|
||||
|
||||
return User::create([
|
||||
$user = User::create([
|
||||
'name' => $input['name'],
|
||||
'email' => $input['email'],
|
||||
'password' => $input['password'],
|
||||
]);
|
||||
|
||||
return $this->avatars->generate($user);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,121 @@
|
||||
<?php
|
||||
|
||||
namespace App\Actions\Users;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\Http\UploadedFile;
|
||||
use Illuminate\Support\Facades\Storage;
|
||||
use Illuminate\Support\Str;
|
||||
use RuntimeException;
|
||||
|
||||
class UpdateUserAvatar
|
||||
{
|
||||
/**
|
||||
* @var list<string>
|
||||
*/
|
||||
private const COLORS = [
|
||||
'#F97316',
|
||||
'#22C55E',
|
||||
'#06B6D4',
|
||||
'#EC4899',
|
||||
'#8B5CF6',
|
||||
'#EAB308',
|
||||
'#EF4444',
|
||||
'#14B8A6',
|
||||
];
|
||||
|
||||
public function generate(User $user): User
|
||||
{
|
||||
$oldPath = $user->avatar_path;
|
||||
$color = $this->colorFor($user);
|
||||
$path = sprintf('avatars/generated/%d-%s.svg', $user->id, (string) Str::uuid());
|
||||
|
||||
$stored = Storage::disk('public')->put(
|
||||
$path,
|
||||
$this->svg($this->initials($user->name), $color),
|
||||
);
|
||||
|
||||
if (! $stored) {
|
||||
throw new RuntimeException('Generated avatar could not be stored.');
|
||||
}
|
||||
|
||||
$user->forceFill([
|
||||
'avatar_path' => $path,
|
||||
'avatar_color' => $color,
|
||||
'avatar_is_generated' => true,
|
||||
])->saveQuietly();
|
||||
|
||||
$this->deletePath($oldPath);
|
||||
|
||||
return $user->refresh();
|
||||
}
|
||||
|
||||
public function upload(User $user, UploadedFile $avatar): User
|
||||
{
|
||||
$oldPath = $user->avatar_path;
|
||||
$path = $avatar->store('avatars/uploads', 'public');
|
||||
|
||||
if (! is_string($path)) {
|
||||
throw new RuntimeException('Uploaded avatar could not be stored.');
|
||||
}
|
||||
|
||||
$user->forceFill([
|
||||
'avatar_path' => $path,
|
||||
'avatar_color' => $this->colorFor($user),
|
||||
'avatar_is_generated' => false,
|
||||
])->saveQuietly();
|
||||
|
||||
$this->deletePath($oldPath);
|
||||
|
||||
return $user->refresh();
|
||||
}
|
||||
|
||||
public function delete(User $user): void
|
||||
{
|
||||
$this->deletePath($user->avatar_path);
|
||||
}
|
||||
|
||||
private function colorFor(User $user): string
|
||||
{
|
||||
if (is_string($user->avatar_color) && in_array($user->avatar_color, self::COLORS, true)) {
|
||||
return $user->avatar_color;
|
||||
}
|
||||
|
||||
return self::COLORS[random_int(0, count(self::COLORS) - 1)];
|
||||
}
|
||||
|
||||
private function deletePath(?string $path): void
|
||||
{
|
||||
if (blank($path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
Storage::disk('public')->delete($path);
|
||||
}
|
||||
|
||||
private function initials(string $name): string
|
||||
{
|
||||
$parts = preg_split('/\s+/u', trim($name)) ?: [];
|
||||
|
||||
if ($parts === []) {
|
||||
return '?';
|
||||
}
|
||||
|
||||
$first = mb_substr($parts[0], 0, 1);
|
||||
$last = count($parts) > 1 ? mb_substr($parts[count($parts) - 1], 0, 1) : '';
|
||||
|
||||
return mb_strtoupper($first.$last);
|
||||
}
|
||||
|
||||
private function svg(string $initials, string $color): string
|
||||
{
|
||||
$escapedInitials = htmlspecialchars($initials, ENT_QUOTES | ENT_XML1, 'UTF-8');
|
||||
|
||||
return <<<SVG
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="192" height="192" viewBox="0 0 192 192" role="img" aria-label="Profile avatar">
|
||||
<rect width="192" height="192" rx="42" fill="{$color}"/>
|
||||
<text x="50%" y="52%" dominant-baseline="middle" text-anchor="middle" fill="#ffffff" font-family="Inter, ui-sans-serif, system-ui, sans-serif" font-size="72" font-weight="700">{$escapedInitials}</text>
|
||||
</svg>
|
||||
SVG;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user