From b5f948433b194b026c515c1e931b6b73a9bc688e Mon Sep 17 00:00:00 2001 From: Doar Mihai Date: Wed, 25 Mar 2026 20:39:10 +0200 Subject: [PATCH] some fixes --- app/Http/Requests/StoreWebsiteRequest.php | 37 ++++++++++++- app/Http/Requests/UpdateWebsiteRequest.php | 39 ++++++++++++- app/Models/Website.php | 24 ++++++++ ...3_25_183139_add_slug_to_websites_table.php | 55 +++++++++++++++++++ resources/views/dashboard/show.blade.php | 12 +++- .../reports/audience/geographic.blade.php | 3 +- resources/views/websites/create.blade.php | 2 +- resources/views/websites/edit.blade.php | 2 +- 8 files changed, 164 insertions(+), 10 deletions(-) create mode 100644 database/migrations/2026_03_25_183139_add_slug_to_websites_table.php diff --git a/app/Http/Requests/StoreWebsiteRequest.php b/app/Http/Requests/StoreWebsiteRequest.php index b3c3a12..0862653 100644 --- a/app/Http/Requests/StoreWebsiteRequest.php +++ b/app/Http/Requests/StoreWebsiteRequest.php @@ -11,18 +11,49 @@ class StoreWebsiteRequest extends FormRequest return true; } + protected function prepareForValidation(): void + { + // Normalize domain: remove protocol and www, trim slashes + if ($this->has('domain')) { + $domain = $this->input('domain'); + + // Remove protocol (http://, https://) + $domain = preg_replace('#^https?://#i', '', $domain); + + // Remove www. + $domain = preg_replace('#^www\.#i', '', $domain); + + // Remove trailing slashes and paths + $domain = strtok($domain, '/'); + + // Trim whitespace + $domain = trim($domain); + + $this->merge([ + 'domain' => $domain, + ]); + } + } + public function rules(): array { return [ - 'name' => ['required', 'string', 'max:255'], - 'domain' => ['required', 'string', 'max:255', 'regex:/^([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/'], + 'name' => ['required', 'string', 'min:1', 'max:255'], + 'domain' => [ + 'required', + 'string', + 'max:255', + 'regex:/^([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/', + 'unique:websites,domain,NULL,id,user_id,' . $this->user()->id, + ], ]; } public function messages(): array { return [ - 'domain.regex' => 'Please enter a valid domain (e.g. example.com).', + 'domain.regex' => 'Please enter a valid domain (e.g. example.com or subdomain.example.com).', + 'domain.unique' => 'You already have a website with this domain.', ]; } } diff --git a/app/Http/Requests/UpdateWebsiteRequest.php b/app/Http/Requests/UpdateWebsiteRequest.php index 606aece..117d105 100644 --- a/app/Http/Requests/UpdateWebsiteRequest.php +++ b/app/Http/Requests/UpdateWebsiteRequest.php @@ -11,11 +11,43 @@ class UpdateWebsiteRequest extends FormRequest return true; } + protected function prepareForValidation(): void + { + // Normalize domain: remove protocol and www, trim slashes + if ($this->has('domain')) { + $domain = $this->input('domain'); + + // Remove protocol (http://, https://) + $domain = preg_replace('#^https?://#i', '', $domain); + + // Remove www. + $domain = preg_replace('#^www\.#i', '', $domain); + + // Remove trailing slashes and paths + $domain = strtok($domain, '/'); + + // Trim whitespace + $domain = trim($domain); + + $this->merge([ + 'domain' => $domain, + ]); + } + } + public function rules(): array { + $websiteId = $this->route('website')->id; + return [ - 'name' => ['required', 'string', 'max:255'], - 'domain' => ['required', 'string', 'max:255', 'regex:/^([a-zA-Z0-9-]+\.)+[a-zA-Z]{2,}$/'], + 'name' => ['required', 'string', 'min:1', 'max:255'], + 'domain' => [ + 'required', + 'string', + 'max:255', + 'regex:/^([a-zA-Z0-9]([a-zA-Z0-9-]*[a-zA-Z0-9])?\.)+[a-zA-Z]{2,}$/', + 'unique:websites,domain,' . $websiteId . ',id,user_id,' . $this->user()->id, + ], 'is_active' => ['sometimes', 'boolean'], ]; } @@ -23,7 +55,8 @@ class UpdateWebsiteRequest extends FormRequest public function messages(): array { return [ - 'domain.regex' => 'Please enter a valid domain (e.g. example.com).', + 'domain.regex' => 'Please enter a valid domain (e.g. example.com or subdomain.example.com).', + 'domain.unique' => 'You already have a website with this domain.', ]; } } diff --git a/app/Models/Website.php b/app/Models/Website.php index a1d4dd7..f6826eb 100644 --- a/app/Models/Website.php +++ b/app/Models/Website.php @@ -16,6 +16,7 @@ class Website extends Model 'user_id', 'name', 'domain', + 'slug', 'tracking_id', 'is_active', 'settings', @@ -35,9 +36,32 @@ class Website extends Model if (empty($website->tracking_id)) { $website->tracking_id = 'TA-' . strtoupper(Str::random(10)); } + + // Auto-generate slug from name if not provided + if (empty($website->slug)) { + $baseSlug = Str::slug($website->name); + $slug = $baseSlug; + $counter = 1; + + // Ensure slug uniqueness + while (static::where('slug', $slug)->exists()) { + $slug = $baseSlug . '-' . $counter; + $counter++; + } + + $website->slug = $slug; + } }); } + /** + * Get the route key for the model. + */ + public function getRouteKeyName(): string + { + return 'slug'; + } + public function user(): BelongsTo { return $this->belongsTo(User::class); diff --git a/database/migrations/2026_03_25_183139_add_slug_to_websites_table.php b/database/migrations/2026_03_25_183139_add_slug_to_websites_table.php new file mode 100644 index 0000000..31e87c2 --- /dev/null +++ b/database/migrations/2026_03_25_183139_add_slug_to_websites_table.php @@ -0,0 +1,55 @@ +string('slug')->nullable()->after('domain'); + $table->unique('slug'); + $table->index('slug'); + }); + + // Generate slugs for existing websites + $websites = DB::table('websites')->get(); + foreach ($websites as $website) { + $baseSlug = Str::slug($website->name); + $slug = $baseSlug; + $counter = 1; + + // Ensure uniqueness + while (DB::table('websites')->where('slug', $slug)->where('id', '!=', $website->id)->exists()) { + $slug = $baseSlug . '-' . $counter; + $counter++; + } + + DB::table('websites')->where('id', $website->id)->update(['slug' => $slug]); + } + + // Make slug non-nullable after populating + Schema::table('websites', function (Blueprint $table) { + $table->string('slug')->nullable(false)->change(); + }); + } + + /** + * Reverse the migrations. + */ + public function down(): void + { + Schema::table('websites', function (Blueprint $table) { + $table->dropUnique(['slug']); + $table->dropIndex(['slug']); + $table->dropColumn('slug'); + }); + } +}; diff --git a/resources/views/dashboard/show.blade.php b/resources/views/dashboard/show.blade.php index 29d4e41..b6bb187 100644 --- a/resources/views/dashboard/show.blade.php +++ b/resources/views/dashboard/show.blade.php @@ -182,7 +182,17 @@ responsive: true, maintainAspectRatio: true, cutout: '65%', - plugins: { legend: { display: false }, tooltip: { backgroundColor: 'rgba(17,24,39,0.95)', padding: 10, cornerRadius: 8 } }, + plugins: { + legend: { display: false }, + tooltip: { + backgroundColor: 'rgba(17,24,39,0.95)', + padding: 10, + cornerRadius: 8, + callbacks: { + label: (ctx) => `${ctx.label}: ${ctx.parsed.toLocaleString()} sessions` + } + } + }, }, }); } diff --git a/resources/views/reports/audience/geographic.blade.php b/resources/views/reports/audience/geographic.blade.php index e85d219..ed00546 100644 --- a/resources/views/reports/audience/geographic.blade.php +++ b/resources/views/reports/audience/geographic.blade.php @@ -76,8 +76,9 @@ cornerRadius: 8, callbacks: { label: (ctx) => { + const countryName = ctx.raw?.feature?.properties?.name || 'Unknown'; const val = ctx.raw?.value || 0; - return val > 0 ? `${ctx.label}: ${val.toLocaleString()} visitors` : `${ctx.label}: 0`; + return val > 0 ? `${countryName}: ${val.toLocaleString()} visitors` : `${countryName}: 0 visitors`; } } } diff --git a/resources/views/websites/create.blade.php b/resources/views/websites/create.blade.php index c6c453c..62955b7 100644 --- a/resources/views/websites/create.blade.php +++ b/resources/views/websites/create.blade.php @@ -21,7 +21,7 @@
-

{{ __('Enter the domain without http:// or https://') }}

+

{{ __('Enter your domain in any format (e.g., example.com, www.example.com, https://example.com)') }}

diff --git a/resources/views/websites/edit.blade.php b/resources/views/websites/edit.blade.php index ee45fef..cd27590 100644 --- a/resources/views/websites/edit.blade.php +++ b/resources/views/websites/edit.blade.php @@ -22,7 +22,7 @@
-

{{ __('Enter the domain without http:// or https://') }}

+

{{ __('Enter your domain in any format (e.g., example.com, www.example.com, https://example.com)') }}