diff --git a/app/Http/Controllers/TrainingController.php b/app/Http/Controllers/TrainingController.php index 43de785b3..2e6ebe695 100644 --- a/app/Http/Controllers/TrainingController.php +++ b/app/Http/Controllers/TrainingController.php @@ -18,6 +18,17 @@ public function show(string $slug): View { $trainingResource = TrainingResource::active()->where('slug', $slug)->firstOrFail(); - return view('training.show', compact('trainingResource')); + return view('training.show', [ + 'trainingResource' => $trainingResource, + 'previewMode' => false, + ]); + } + + public function preview(TrainingResource $trainingResource): View + { + return view('training.show', [ + 'trainingResource' => $trainingResource, + 'previewMode' => true, + ]); } } diff --git a/app/Nova/TrainingResource.php b/app/Nova/TrainingResource.php index 1a32706d3..e54295723 100644 --- a/app/Nova/TrainingResource.php +++ b/app/Nova/TrainingResource.php @@ -3,6 +3,7 @@ namespace App\Nova; use Illuminate\Http\Request; +use Illuminate\Support\Facades\URL; use Laravel\Nova\Fields\Boolean; use Laravel\Nova\Fields\ID; use Laravel\Nova\Fields\Number; @@ -45,6 +46,23 @@ public function fields(Request $request): array ->rules('nullable', 'max:255', 'alpha_dash', 'unique:training_resources,slug,{{resourceId}}') ->help('Optional. If empty, generated automatically from title. Used in /training/{slug}.'), + Text::make('Preview URL', function () { + if (! $this->resource?->exists) { + return 'Save first to generate preview URL.'; + } + + $url = URL::temporarySignedRoute( + 'training.preview', + now()->addDays(14), + ['trainingResource' => $this->resource] + ); + + return ''.$url.''; + }) + ->onlyOnDetail() + ->asHtml() + ->help('Share this link with clients for preview before publishing. Link expires in 14 days.'), + Text::make('Card title', 'card_title') ->rules('nullable', 'max:255') ->help('Optional. Shown in the Learning Bits grid on /training'), @@ -133,7 +151,8 @@ public function fields(Request $request): array ->help('Lower = shown first among dynamic resources') ->nullable(), - Boolean::make('Active', 'active'), + Boolean::make('Published', 'active') + ->help('Turn off to keep this page hidden publicly. Preview URL still works.'), ]; } diff --git a/resources/views/training/show.blade.php b/resources/views/training/show.blade.php index 317f5e2ba..373bc6adc 100644 --- a/resources/views/training/show.blade.php +++ b/resources/views/training/show.blade.php @@ -22,6 +22,14 @@ @section('content')
+ @if(($previewMode ?? false) === true) +
+
+ Preview mode: this page is not published yet. +
+
+ @endif + @include('codingathome.banner', [ 'author' => $trainingResource->hero_author, 'title' => $displayTitle, diff --git a/routes/web.php b/routes/web.php index 21c7eaed2..ff5ce9602 100644 --- a/routes/web.php +++ b/routes/web.php @@ -339,6 +339,9 @@ '/training/making-and-coding', [StaticPageController::class, 'static'] )->name('training.module-22'); */ +Route::get('/training-preview/{trainingResource}', [TrainingController::class, 'preview']) + ->middleware('signed') + ->name('training.preview'); Route::get('/training/{slug}', [TrainingController::class, 'show'])->name('training.dynamic.show'); Route::post('/contact-submit', [ContactFormController::class, 'submit']) ->middleware('throttle:5,1') // 5 requests per minute per IP