feat(bio): Link-in-Bio pages with translatable fields, public render
parent
d7ca84dd2e
commit
e9623b9b31
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
namespace App\Domains\Bio\Actions;
|
||||
|
||||
use App\Domains\Bio\Models\BioPage;
|
||||
use App\Models\User;
|
||||
|
||||
class CreateBioPage
|
||||
{
|
||||
public function handle(int $workspaceId, array $data, User $creator): BioPage
|
||||
{
|
||||
return BioPage::create([
|
||||
'workspace_id' => $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,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
<?php
|
||||
|
||||
namespace App\Domains\Bio\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
class BioBlock extends Model
|
||||
{
|
||||
protected $guarded = ['id'];
|
||||
|
||||
protected $casts = [
|
||||
'config' => 'array',
|
||||
];
|
||||
|
||||
public function bioPage(): \Illuminate\Database\Eloquent\Relations\BelongsTo
|
||||
{
|
||||
return $this->belongsTo(BioPage::class);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
namespace App\Domains\Bio\Models;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Support\Str;
|
||||
use Spatie\Translatable\HasTranslations;
|
||||
|
||||
class BioPage extends Model
|
||||
{
|
||||
use SoftDeletes, HasFactory, HasTranslations;
|
||||
|
||||
protected $guarded = ['id'];
|
||||
|
||||
public array $translatable = ['title', 'description'];
|
||||
|
||||
protected $casts = [
|
||||
'theme' => '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);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
<?php
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Domains\Bio\Models\BioPage;
|
||||
use App\Domains\Domain\Models\Domain;
|
||||
|
||||
class BioPageController extends Controller
|
||||
{
|
||||
public function show(string $slug): \Illuminate\View\View|\Illuminate\Http\Response
|
||||
{
|
||||
$host = request()->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'));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="{{ app()->getLocale() }}">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>{{ $page->getTranslation('title', app()->getLocale()) }}</title>
|
||||
@vite(['resources/css/app.css'])
|
||||
</head>
|
||||
<body class="bg-gray-950 text-gray-100 min-h-screen flex flex-col items-center py-12">
|
||||
<div class="w-full max-w-md">
|
||||
<h1 class="text-2xl font-bold text-center mb-2">
|
||||
{{ $page->getTranslation('title', app()->getLocale()) }}
|
||||
</h1>
|
||||
@if($page->getTranslation('description', app()->getLocale()))
|
||||
<p class="text-gray-400 text-center mb-8">
|
||||
{{ $page->getTranslation('description', app()->getLocale()) }}
|
||||
</p>
|
||||
@endif
|
||||
<div class="space-y-3">
|
||||
@foreach($page->blocks as $block)
|
||||
<div class="bg-gray-900 rounded-xl p-4 border border-gray-800">
|
||||
<div class="text-sm text-gray-300">{{ $block->config['content'] ?? '' }}</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in New Issue