From e9623b9b3118cb17e4183d8d6fe1cc80f6bff05f Mon Sep 17 00:00:00 2001 From: boban Date: Sat, 16 May 2026 08:10:59 +0200 Subject: [PATCH] feat(bio): Link-in-Bio pages with translatable fields, public render --- app/Domains/Bio/Actions/CreateBioPage.php | 22 ++++++++++ app/Domains/Bio/Models/BioBlock.php | 19 +++++++++ app/Domains/Bio/Models/BioPage.php | 48 ++++++++++++++++++++++ app/Http/Controllers/BioPageController.php | 23 +++++++++++ resources/views/bio/show.blade.php | 28 +++++++++++++ routes/web.php | 3 ++ 6 files changed, 143 insertions(+) create mode 100644 app/Domains/Bio/Actions/CreateBioPage.php create mode 100644 app/Domains/Bio/Models/BioBlock.php create mode 100644 app/Domains/Bio/Models/BioPage.php create mode 100644 app/Http/Controllers/BioPageController.php create mode 100644 resources/views/bio/show.blade.php diff --git a/app/Domains/Bio/Actions/CreateBioPage.php b/app/Domains/Bio/Actions/CreateBioPage.php new file mode 100644 index 0000000..51aff63 --- /dev/null +++ b/app/Domains/Bio/Actions/CreateBioPage.php @@ -0,0 +1,22 @@ + $workspaceId, + 'slug' => $data['slug'], + 'title' => $data['title'] ?? [], + 'description' => $data['description'] ?? [], + 'theme' => $data['theme'] ?? [], + 'is_published' => $data['is_published'] ?? false, + 'domain_id' => $data['domain_id'] ?? null, + ]); + } +} diff --git a/app/Domains/Bio/Models/BioBlock.php b/app/Domains/Bio/Models/BioBlock.php new file mode 100644 index 0000000..1e40887 --- /dev/null +++ b/app/Domains/Bio/Models/BioBlock.php @@ -0,0 +1,19 @@ + 'array', + ]; + + public function bioPage(): \Illuminate\Database\Eloquent\Relations\BelongsTo + { + return $this->belongsTo(BioPage::class); + } +} diff --git a/app/Domains/Bio/Models/BioPage.php b/app/Domains/Bio/Models/BioPage.php new file mode 100644 index 0000000..15500c5 --- /dev/null +++ b/app/Domains/Bio/Models/BioPage.php @@ -0,0 +1,48 @@ + 'array', + 'is_published' => 'boolean', + 'ulid' => 'string', + ]; + + protected static function booted(): void + { + static::creating(function (self $model) { + if (empty($model->ulid)) { + $model->ulid = (string) Str::ulid(); + } + }); + } + + public function blocks(): \Illuminate\Database\Eloquent\Relations\HasMany + { + return $this->hasMany(BioBlock::class)->orderBy('position'); + } + + public function workspace(): \Illuminate\Database\Eloquent\Relations\BelongsTo + { + return $this->belongsTo(\App\Domains\Workspace\Models\Workspace::class); + } + + public function domain(): \Illuminate\Database\Eloquent\Relations\BelongsTo + { + return $this->belongsTo(\App\Domains\Domain\Models\Domain::class); + } +} diff --git a/app/Http/Controllers/BioPageController.php b/app/Http/Controllers/BioPageController.php new file mode 100644 index 0000000..41bcdc2 --- /dev/null +++ b/app/Http/Controllers/BioPageController.php @@ -0,0 +1,23 @@ +getHost(); + $domain = Domain::where('hostname', $host)->first(); + + $page = BioPage::where('slug', $slug) + ->where('domain_id', $domain?->id) + ->where('is_published', true) + ->with('blocks') + ->firstOrFail(); + + return view('bio.show', compact('page')); + } +} diff --git a/resources/views/bio/show.blade.php b/resources/views/bio/show.blade.php new file mode 100644 index 0000000..aeb35d9 --- /dev/null +++ b/resources/views/bio/show.blade.php @@ -0,0 +1,28 @@ + + + + + + {{ $page->getTranslation('title', app()->getLocale()) }} + @vite(['resources/css/app.css']) + + +
+

+ {{ $page->getTranslation('title', app()->getLocale()) }} +

+ @if($page->getTranslation('description', app()->getLocale())) +

+ {{ $page->getTranslation('description', app()->getLocale()) }} +

+ @endif +
+ @foreach($page->blocks as $block) +
+
{{ $block->config['content'] ?? '' }}
+
+ @endforeach +
+
+ + diff --git a/routes/web.php b/routes/web.php index 7b7d696..5cbbe84 100644 --- a/routes/web.php +++ b/routes/web.php @@ -30,6 +30,9 @@ Route::middleware(['auth', 'verified', \App\Http\Middleware\ResolveWorkspace::cl Route::get('/billing', \App\Livewire\Pages\Billing\Index::class)->name('billing.index'); }); +// Bio page route — must be before the slug catch-all +Route::get('/bio/{slug}', [\App\Http\Controllers\BioPageController::class, 'show'])->name('bio.public'); + // Redirect service — handles slugs on main domain and custom domains // Placed after all named routes so dashboard/profile etc. are not caught here Route::get('/{slug}', \App\Http\Controllers\RedirectController::class)