Skip to content

Commit 0e5cd14

Browse files
committed
fix(auth): Improve redirect UX and international avatar support
1 parent 1c81709 commit 0e5cd14

File tree

3 files changed

+79
-54
lines changed

3 files changed

+79
-54
lines changed

app/Http/Controllers/Auth/AuthController.php

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1022,7 +1022,8 @@ public function telegramCallback(Request $request): RedirectResponse
10221022
'total_callback_duration_seconds' => microtime(true) - $time_start_callback,
10231023
]);
10241024

1025-
return redirect()->intended(route('home'))->with('success', __('messages.telegram_login_success'));
1025+
// return redirect()->intended(route('home'))->with('success', __('messages.telegram_login_success'));
1026+
return redirect()->route('home')->with('success', __('messages.telegram_login_success'));
10261027

10271028
} catch (Exception $e) {
10281029
Log::channel('audit_trail')->error('Telegram authentication/callback failed with generic Exception.', [
@@ -1048,9 +1049,12 @@ private function handleTelegramUser(array $telegramUser): User
10481049
if (!$user->exists) {
10491050
$baseName = $telegramUser['username'] ?? ($telegramUser['first_name'] . ($telegramUser['last_name'] ?? ''));
10501051

1052+
$firstName = preg_replace('/[^\\p{L}\\p{N}\\s]/u', '', $telegramUser['first_name']);
1053+
$lastName = preg_replace('/[^\\p{L}\\p{N}\\s]/u', '', $telegramUser['last_name'] ?? '');
1054+
10511055
$user->fill([
1052-
'first_name' => $telegramUser['first_name'],
1053-
'last_name' => $telegramUser['last_name'] ?? null,
1056+
'first_name' => $firstName,
1057+
'last_name' => $lastName,
10541058
'username' => $this->generateUniqueUsername($baseName, $telegramUser['id']),
10551059
'email' => $telegramUser['id'] . '@telegram-user.local',
10561060
'email_verified_at' => now(),

app/Services/AvatarService.php

Lines changed: 67 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -2,83 +2,103 @@
22

33
namespace App\Services;
44

5+
use Exception;
6+
use Illuminate\Support\Facades\Log;
57
use Illuminate\Support\Facades\Storage;
8+
use Illuminate\Support\Str;
69
use Intervention\Image\Drivers\Gd\Driver as GdDriver;
10+
use Intervention\Image\Encoders\WebpEncoder;
711
use Intervention\Image\ImageManager;
812
use Intervention\Image\Typography\FontFactory;
913

10-
1114
class AvatarService
1215
{
13-
final public function generateInitialsAvatar(string $firstName, string $lastName, string $userId, int $size = 200): string
14-
{
15-
$firstInitial = mb_substr($firstName, 0, 1);
16-
$lastInitial = !empty($lastName) ? mb_substr($lastName, 0, 1) : '';
17-
$initials = mb_strtoupper($firstInitial . $lastInitial);
18-
19-
$manager = new ImageManager(new GdDriver());
20-
21-
$hash = md5($userId);
22-
$hue = hexdec(substr($hash, 0, 2)) % 360;
23-
$backgroundColor = $this->hsvToRgb($hue, 0.7, 0.9);
24-
25-
$image = $manager->create($size, $size)->fill($backgroundColor);
26-
27-
$image->text($initials, $size / 2, $size / 2, function (FontFactory $font) use ($size) {
28-
$font->file(public_path('fonts/poppins.ttf'));
29-
$font->size($size * 0.4);
30-
$font->color('#ffffff');
31-
$font->align('center');
32-
$font->valign('middle');
33-
});
34-
35-
$path = 'profile_pictures/initial_' . $userId . '.png';
36-
37-
$encodedImage = $image->toPng()->toString();
16+
private const AVATAR_SIZE = 200;
17+
private const FONT_SIZE_RATIO = 0.45;
18+
private const IMAGE_QUALITY = 85;
3819

39-
Storage::disk('public')->put($path, $encodedImage);
20+
final public function generateInitialsAvatar(string $firstName, string $lastName = '', string $userId): string
21+
{
22+
try {
23+
$firstInitial = !empty($firstName) ? mb_strtoupper(mb_substr($firstName, 0, 1, 'UTF-8')) : '';
24+
$lastInitial = !empty($lastName) ? mb_strtoupper(mb_substr($lastName, 0, 1, 'UTF-8')) : '';
25+
$initials = $firstInitial . $lastInitial;
26+
27+
if (empty(trim($initials))) {
28+
$initials = '?';
29+
}
30+
31+
$fontPath = storage_path('app/fonts/NotoSans-Regular.ttf');
32+
if (!file_exists($fontPath)) {
33+
Log::error('AvatarService: Font not found at path: ' . $fontPath);
34+
return 'images/avatars/default.png';
35+
}
36+
37+
$manager = new ImageManager(new GdDriver());
38+
$image = $manager->create(self::AVATAR_SIZE, self::AVATAR_SIZE);
39+
40+
$backgroundColor = $this->generateBackgroundColor($userId);
41+
$image->fill($backgroundColor);
42+
43+
$image->text($initials, self::AVATAR_SIZE / 2, self::AVATAR_SIZE / 2, function (FontFactory $font) use ($fontPath) {
44+
$font->file($fontPath);
45+
$font->size(self::AVATAR_SIZE * self::FONT_SIZE_RATIO);
46+
$font->color('#FFFFFF');
47+
$font->align('center');
48+
$font->valign('middle');
49+
});
50+
51+
$path = 'profile_pictures/initials_' . $userId . '_' . Str::random(5) . '.webp';
52+
$encodedImage = $image->encode(new WebpEncoder(quality: self::IMAGE_QUALITY));
53+
Storage::disk('public')->put($path, $encodedImage);
54+
55+
return $path;
56+
57+
} catch (Exception $e) {
58+
Log::error('Failed to generate initials avatar for user ' . $userId, [
59+
'error' => $e->getMessage(),
60+
'trace' => $e->getTraceAsString(),
61+
]);
62+
return 'images/avatars/default.png';
63+
}
64+
}
4065

41-
return $path;
66+
private function generateBackgroundColor(string $seed): string
67+
{
68+
$hash = crc32($seed);
69+
$hue = $hash % 360;
70+
$saturation = 0.5;
71+
$value = 0.8;
72+
return $this->hsvToRgbString($hue, $saturation, $value);
4273
}
4374

44-
private function hsvToRgb(float $h, float $s, float $v): string
75+
private function hsvToRgbString(float $h, float $s, float $v): string
4576
{
4677
$h_i = floor($h / 60) % 6;
4778
$f = $h / 60 - $h_i;
4879
$p = $v * (1 - $s);
4980
$q = $v * (1 - $f * $s);
5081
$t = $v * (1 - (1 - $f) * $s);
51-
52-
$r_float = 0.0;
53-
$g_float = 0.0;
54-
$b_float = 0.0;
55-
5682
switch ($h_i) {
5783
case 0:
58-
[$r_float, $g_float, $b_float] = [$v, $t, $p];
84+
list($r, $g, $b) = [$v, $t, $p];
5985
break;
6086
case 1:
61-
[$r_float, $g_float, $b_float] = [$q, $v, $p];
87+
list($r, $g, $b) = [$q, $v, $p];
6288
break;
6389
case 2:
64-
[$r_float, $g_float, $b_float] = [$p, $v, $t];
90+
list($r, $g, $b) = [$p, $v, $t];
6591
break;
6692
case 3:
67-
[$r_float, $g_float, $b_float] = [$p, $q, $v];
93+
list($r, $g, $b) = [$p, $q, $v];
6894
break;
6995
case 4:
70-
[$r_float, $g_float, $b_float] = [$t, $p, $v];
96+
list($r, $g, $b) = [$t, $p, $v];
7197
break;
72-
case 5:
7398
default:
74-
[$r_float, $g_float, $b_float] = [$v, $p, $q];
99+
list($r, $g, $b) = [$v, $p, $q];
75100
break;
76101
}
77-
78-
$r = round($r_float * 255);
79-
$g = round($g_float * 255);
80-
$b = round($b_float * 255);
81-
82-
return "rgba($r, $g, $b, 1)";
102+
return sprintf("#%02x%02x%02x", round($r * 255), round($g * 255), round($b * 255));
83103
}
84104
}

resources/views/layouts/app.blade.php

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -827,13 +827,14 @@ function loadGoogleAds() {
827827
const urlFragment = window.location.hash.substring(1);
828828
829829
if (urlFragment.startsWith('tgAuthResult=')) {
830-
document.documentElement.style.backgroundColor = '#f9fafb';
831-
document.body.style.display = 'none';
830+
document.documentElement.style.visibility = 'hidden';
831+
// document.documentElement.style.backgroundColor = '#f9fafb';
832+
// document.body.style.display = 'none';
832833
833834
const loader = document.createElement('div');
834835
loader.textContent = 'Authenticating...';
835-
loader.style.cssText = 'position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%); font-size: 1.25rem; color: #4b5563; font-family: sans-serif;';
836-
document.body.insertAdjacentElement('afterend', loader);
836+
loader.style.cssText = 'position: fixed; top: 0; left: 0; width: 100%; height: 100%; display: flex; align-items: center; justify-content: center; background-color: #f9fafb; font-size: 1.25rem; color: #4b5563; font-family: sans-serif; z-index: 9999;';
837+
document.body.appendChild(loader);
837838
838839
try {
839840
const encodedData = urlFragment.substring('tgAuthResult='.length);

0 commit comments

Comments
 (0)