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');
|
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
|
// Redirect service — handles slugs on main domain and custom domains
|
||||||
// Placed after all named routes so dashboard/profile etc. are not caught here
|
// Placed after all named routes so dashboard/profile etc. are not caught here
|
||||||
Route::get('/{slug}', \App\Http\Controllers\RedirectController::class)
|
Route::get('/{slug}', \App\Http\Controllers\RedirectController::class)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue