Add onboarding
This commit is contained in:
parent
833cf7a313
commit
8de4368e50
10 changed files with 573 additions and 4 deletions
|
|
@ -29,7 +29,7 @@ public function store(Request $request): RedirectResponse
|
||||||
'name' => 'required|string|max:255',
|
'name' => 'required|string|max:255',
|
||||||
'url' => 'required|url|unique:feeds,url',
|
'url' => 'required|url|unique:feeds,url',
|
||||||
'type' => 'required|in:website,rss',
|
'type' => 'required|in:website,rss',
|
||||||
'language' => 'required|string|size:2',
|
'language_id' => 'required|exists:languages,id',
|
||||||
'description' => 'nullable|string',
|
'description' => 'nullable|string',
|
||||||
'is_active' => 'boolean'
|
'is_active' => 'boolean'
|
||||||
]);
|
]);
|
||||||
|
|
@ -39,6 +39,13 @@ public function store(Request $request): RedirectResponse
|
||||||
|
|
||||||
Feed::create($validated);
|
Feed::create($validated);
|
||||||
|
|
||||||
|
// Check if there's a redirect_to parameter for onboarding flow
|
||||||
|
$redirectTo = $request->input('redirect_to');
|
||||||
|
if ($redirectTo) {
|
||||||
|
return redirect($redirectTo)
|
||||||
|
->with('success', 'Feed created successfully!');
|
||||||
|
}
|
||||||
|
|
||||||
return redirect()->route('feeds.index')
|
return redirect()->route('feeds.index')
|
||||||
->with('success', 'Feed created successfully!');
|
->with('success', 'Feed created successfully!');
|
||||||
}
|
}
|
||||||
|
|
@ -59,7 +66,7 @@ public function update(Request $request, Feed $feed): RedirectResponse
|
||||||
'name' => 'required|string|max:255',
|
'name' => 'required|string|max:255',
|
||||||
'url' => 'required|url|unique:feeds,url,' . $feed->id,
|
'url' => 'required|url|unique:feeds,url,' . $feed->id,
|
||||||
'type' => 'required|in:website,rss',
|
'type' => 'required|in:website,rss',
|
||||||
'language' => 'required|string|size:2',
|
'language_id' => 'required|exists:languages,id',
|
||||||
'description' => 'nullable|string',
|
'description' => 'nullable|string',
|
||||||
'is_active' => 'boolean'
|
'is_active' => 'boolean'
|
||||||
]);
|
]);
|
||||||
|
|
|
||||||
96
app/Http/Controllers/OnboardingController.php
Normal file
96
app/Http/Controllers/OnboardingController.php
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\Feed;
|
||||||
|
use App\Models\PlatformAccount;
|
||||||
|
use App\Models\PlatformChannel;
|
||||||
|
use App\Models\PlatformInstance;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\View\View;
|
||||||
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
|
||||||
|
class OnboardingController extends Controller
|
||||||
|
{
|
||||||
|
public function index(): View|RedirectResponse
|
||||||
|
{
|
||||||
|
// Check if user needs onboarding
|
||||||
|
if (!$this->needsOnboarding()) {
|
||||||
|
return redirect()->route('feeds.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('onboarding.welcome');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function platform(): View|RedirectResponse
|
||||||
|
{
|
||||||
|
if (!$this->needsOnboarding()) {
|
||||||
|
return redirect()->route('feeds.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('onboarding.platform');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function feed(): View|RedirectResponse
|
||||||
|
{
|
||||||
|
if (!$this->needsOnboarding()) {
|
||||||
|
return redirect()->route('feeds.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->hasPlatformAccount()) {
|
||||||
|
return redirect()->route('onboarding.platform');
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('onboarding.feed');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function channel(): View|RedirectResponse
|
||||||
|
{
|
||||||
|
if (!$this->needsOnboarding()) {
|
||||||
|
return redirect()->route('feeds.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->hasPlatformAccount()) {
|
||||||
|
return redirect()->route('onboarding.platform');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->hasFeed()) {
|
||||||
|
return redirect()->route('onboarding.feed');
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('onboarding.channel');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function complete(): View|RedirectResponse
|
||||||
|
{
|
||||||
|
if (!$this->needsOnboarding()) {
|
||||||
|
return redirect()->route('feeds.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!$this->hasPlatformAccount() || !$this->hasFeed() || !$this->hasChannel()) {
|
||||||
|
return redirect()->route('onboarding.index');
|
||||||
|
}
|
||||||
|
|
||||||
|
return view('onboarding.complete');
|
||||||
|
}
|
||||||
|
|
||||||
|
private function needsOnboarding(): bool
|
||||||
|
{
|
||||||
|
return !$this->hasPlatformAccount() || !$this->hasFeed() || !$this->hasChannel();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function hasPlatformAccount(): bool
|
||||||
|
{
|
||||||
|
return PlatformAccount::where('is_active', true)->exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function hasFeed(): bool
|
||||||
|
{
|
||||||
|
return Feed::where('is_active', true)->exists();
|
||||||
|
}
|
||||||
|
|
||||||
|
private function hasChannel(): bool
|
||||||
|
{
|
||||||
|
return PlatformChannel::where('is_active', true)->exists();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,6 +3,8 @@
|
||||||
namespace App\Http\Controllers;
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
use App\Models\PlatformAccount;
|
use App\Models\PlatformAccount;
|
||||||
|
use App\Models\PlatformInstance;
|
||||||
|
use App\Enums\PlatformEnum;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Illuminate\Contracts\View\View;
|
use Illuminate\Contracts\View\View;
|
||||||
use Illuminate\Http\RedirectResponse;
|
use Illuminate\Http\RedirectResponse;
|
||||||
|
|
@ -31,6 +33,17 @@ public function store(Request $request): RedirectResponse
|
||||||
'settings' => 'nullable|array',
|
'settings' => 'nullable|array',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Create or find platform instance
|
||||||
|
$platformEnum = PlatformEnum::from($validated['platform']);
|
||||||
|
$instance = PlatformInstance::firstOrCreate([
|
||||||
|
'platform' => $platformEnum,
|
||||||
|
'url' => $validated['instance_url'],
|
||||||
|
], [
|
||||||
|
'name' => parse_url($validated['instance_url'], PHP_URL_HOST),
|
||||||
|
'description' => ucfirst($validated['platform']) . ' instance',
|
||||||
|
'is_active' => true,
|
||||||
|
]);
|
||||||
|
|
||||||
$account = PlatformAccount::create($validated);
|
$account = PlatformAccount::create($validated);
|
||||||
|
|
||||||
// If this is the first account for this platform, make it active
|
// If this is the first account for this platform, make it active
|
||||||
|
|
@ -38,6 +51,13 @@ public function store(Request $request): RedirectResponse
|
||||||
$account->setAsActive();
|
$account->setAsActive();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Check if there's a redirect_to parameter for onboarding flow
|
||||||
|
$redirectTo = $request->input('redirect_to');
|
||||||
|
if ($redirectTo) {
|
||||||
|
return redirect($redirectTo)
|
||||||
|
->with('success', 'Platform account created successfully!');
|
||||||
|
}
|
||||||
|
|
||||||
return redirect()->route('platforms.index')
|
return redirect()->route('platforms.index')
|
||||||
->with('success', 'Platform account created successfully!');
|
->with('success', 'Platform account created successfully!');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -34,13 +34,28 @@ public function store(Request $request): RedirectResponse
|
||||||
$validated = $request->validate([
|
$validated = $request->validate([
|
||||||
'platform_instance_id' => 'required|exists:platform_instances,id',
|
'platform_instance_id' => 'required|exists:platform_instances,id',
|
||||||
'name' => 'required|string|max:255',
|
'name' => 'required|string|max:255',
|
||||||
'display_name' => 'required|string|max:255',
|
'display_name' => 'nullable|string|max:255',
|
||||||
'channel_id' => 'required|string|max:255',
|
'channel_id' => 'nullable|string|max:255',
|
||||||
'description' => 'nullable|string',
|
'description' => 'nullable|string',
|
||||||
|
'language_id' => 'required|exists:languages,id',
|
||||||
|
'is_active' => 'boolean',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
|
// Default is_active to true if not provided
|
||||||
|
$validated['is_active'] = $validated['is_active'] ?? true;
|
||||||
|
|
||||||
|
// Set display_name to name if not provided
|
||||||
|
$validated['display_name'] = $validated['display_name'] ?? $validated['name'];
|
||||||
|
|
||||||
PlatformChannel::create($validated);
|
PlatformChannel::create($validated);
|
||||||
|
|
||||||
|
// Check if there's a redirect_to parameter for onboarding flow
|
||||||
|
$redirectTo = $request->input('redirect_to');
|
||||||
|
if ($redirectTo) {
|
||||||
|
return redirect($redirectTo)
|
||||||
|
->with('success', 'Channel created successfully!');
|
||||||
|
}
|
||||||
|
|
||||||
return redirect()->route('channels.index')
|
return redirect()->route('channels.index')
|
||||||
->with('success', 'Channel created successfully!');
|
->with('success', 'Channel created successfully!');
|
||||||
}
|
}
|
||||||
|
|
|
||||||
110
resources/views/onboarding/channel.blade.php
Normal file
110
resources/views/onboarding/channel.blade.php
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="min-h-screen flex items-center justify-center bg-gray-50">
|
||||||
|
<div class="max-w-lg w-full bg-white rounded-lg shadow-md p-8">
|
||||||
|
<div class="text-center mb-8">
|
||||||
|
<h1 class="text-2xl font-bold text-gray-900 mb-2">Configure Your Channel</h1>
|
||||||
|
<p class="text-gray-600">
|
||||||
|
Set up a Lemmy community where articles will be posted
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Progress indicator -->
|
||||||
|
<div class="flex justify-center mt-6 space-x-2">
|
||||||
|
<div class="w-6 h-6 bg-green-500 text-white rounded-full flex items-center justify-center text-xs font-semibold">✓</div>
|
||||||
|
<div class="w-6 h-6 bg-green-500 text-white rounded-full flex items-center justify-center text-xs font-semibold">✓</div>
|
||||||
|
<div class="w-6 h-6 bg-blue-500 text-white rounded-full flex items-center justify-center text-xs font-semibold">3</div>
|
||||||
|
<div class="w-6 h-6 bg-gray-300 text-gray-600 rounded-full flex items-center justify-center text-xs font-semibold">4</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action="{{ route('channels.store') }}" method="POST" class="space-y-6">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="name" class="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Community Name
|
||||||
|
</label>
|
||||||
|
<input type="text"
|
||||||
|
id="name"
|
||||||
|
name="name"
|
||||||
|
value="{{ old('name') }}"
|
||||||
|
placeholder="technology"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
required>
|
||||||
|
<p class="text-sm text-gray-500 mt-1">Enter the community name (without the @ or instance)</p>
|
||||||
|
@error('name')
|
||||||
|
<p class="text-red-600 text-sm mt-1">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="platform_instance_id" class="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Platform Instance
|
||||||
|
</label>
|
||||||
|
<select id="platform_instance_id"
|
||||||
|
name="platform_instance_id"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
required>
|
||||||
|
@foreach(\App\Models\PlatformInstance::where('is_active', true)->get() as $instance)
|
||||||
|
<option value="{{ $instance->id }}" {{ old('platform_instance_id') == $instance->id ? 'selected' : '' }}>
|
||||||
|
{{ $instance->name }} ({{ $instance->url }})
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
@error('platform_instance_id')
|
||||||
|
<p class="text-red-600 text-sm mt-1">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="language_id" class="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Language
|
||||||
|
</label>
|
||||||
|
<select id="language_id"
|
||||||
|
name="language_id"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
required>
|
||||||
|
<option value="">Select language</option>
|
||||||
|
@foreach(\App\Models\Language::orderBy('name')->get() as $language)
|
||||||
|
<option value="{{ $language->id }}" {{ old('language_id') == $language->id ? 'selected' : '' }}>
|
||||||
|
{{ $language->name }}
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
@error('language_id')
|
||||||
|
<p class="text-red-600 text-sm mt-1">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="description" class="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Description (Optional)
|
||||||
|
</label>
|
||||||
|
<textarea id="description"
|
||||||
|
name="description"
|
||||||
|
rows="3"
|
||||||
|
placeholder="Brief description of this channel"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent">{{ old('description') }}</textarea>
|
||||||
|
@error('description')
|
||||||
|
<p class="text-red-600 text-sm mt-1">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="hidden" name="is_active" value="1">
|
||||||
|
<input type="hidden" name="redirect_to" value="{{ route('onboarding.complete') }}">
|
||||||
|
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<a href="{{ route('onboarding.feed') }}"
|
||||||
|
class="px-4 py-2 text-gray-600 hover:text-gray-800 transition duration-200">
|
||||||
|
← Back
|
||||||
|
</a>
|
||||||
|
<button type="submit"
|
||||||
|
class="bg-blue-600 text-white py-2 px-6 rounded-md hover:bg-blue-700 transition duration-200">
|
||||||
|
Continue
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
64
resources/views/onboarding/complete.blade.php
Normal file
64
resources/views/onboarding/complete.blade.php
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="min-h-screen flex items-center justify-center bg-gray-50">
|
||||||
|
<div class="max-w-lg w-full bg-white rounded-lg shadow-md p-8">
|
||||||
|
<div class="text-center">
|
||||||
|
<div class="mb-6">
|
||||||
|
<div class="w-16 h-16 bg-green-500 text-white rounded-full flex items-center justify-center mx-auto mb-4">
|
||||||
|
<svg class="w-8 h-8" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path>
|
||||||
|
</svg>
|
||||||
|
</div>
|
||||||
|
<h1 class="text-3xl font-bold text-gray-900 mb-2">Setup Complete!</h1>
|
||||||
|
<p class="text-gray-600 mb-6">
|
||||||
|
Great! You've successfully configured Lemmy Poster. Your feeds will now be monitored and articles will be automatically posted to your configured channels.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Progress indicator -->
|
||||||
|
<div class="flex justify-center mb-8 space-x-2">
|
||||||
|
<div class="w-6 h-6 bg-green-500 text-white rounded-full flex items-center justify-center text-xs font-semibold">✓</div>
|
||||||
|
<div class="w-6 h-6 bg-green-500 text-white rounded-full flex items-center justify-center text-xs font-semibold">✓</div>
|
||||||
|
<div class="w-6 h-6 bg-green-500 text-white rounded-full flex items-center justify-center text-xs font-semibold">✓</div>
|
||||||
|
<div class="w-6 h-6 bg-green-500 text-white rounded-full flex items-center justify-center text-xs font-semibold">✓</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-4 mb-8">
|
||||||
|
<div class="bg-blue-50 border border-blue-200 rounded-lg p-4">
|
||||||
|
<h3 class="font-semibold text-blue-900 mb-2">What happens next?</h3>
|
||||||
|
<ul class="text-sm text-blue-800 space-y-1 text-left">
|
||||||
|
<li>• Your feeds will be checked regularly for new articles</li>
|
||||||
|
<li>• New articles will be automatically posted to your channels</li>
|
||||||
|
<li>• You can monitor activity in the Articles and Logs sections</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
|
||||||
|
<h3 class="font-semibold text-yellow-900 mb-2">Want more control?</h3>
|
||||||
|
<p class="text-sm text-yellow-800 text-left mb-2">
|
||||||
|
Set up <strong>routing rules</strong> to control which articles get posted where based on keywords, titles, or content.
|
||||||
|
</p>
|
||||||
|
<a href="{{ route('routing.index') }}"
|
||||||
|
class="inline-block bg-yellow-200 hover:bg-yellow-300 text-yellow-900 px-3 py-1 rounded-md text-sm transition duration-200">
|
||||||
|
Configure Routing →
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="space-y-3">
|
||||||
|
<a href="{{ route('feeds.index') }}"
|
||||||
|
class="w-full bg-blue-600 text-white py-3 px-4 rounded-md hover:bg-blue-700 transition duration-200 inline-block">
|
||||||
|
Go to Dashboard
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class="text-sm text-gray-500">
|
||||||
|
<a href="{{ route('articles') }}" class="hover:text-blue-600">View Articles</a>
|
||||||
|
•
|
||||||
|
<a href="{{ route('logs') }}" class="hover:text-blue-600">Check Logs</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
123
resources/views/onboarding/feed.blade.php
Normal file
123
resources/views/onboarding/feed.blade.php
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="min-h-screen flex items-center justify-center bg-gray-50">
|
||||||
|
<div class="max-w-lg w-full bg-white rounded-lg shadow-md p-8">
|
||||||
|
<div class="text-center mb-8">
|
||||||
|
<h1 class="text-2xl font-bold text-gray-900 mb-2">Add Your First Feed</h1>
|
||||||
|
<p class="text-gray-600">
|
||||||
|
Add a RSS feed or website to monitor for new articles
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Progress indicator -->
|
||||||
|
<div class="flex justify-center mt-6 space-x-2">
|
||||||
|
<div class="w-6 h-6 bg-green-500 text-white rounded-full flex items-center justify-center text-xs font-semibold">✓</div>
|
||||||
|
<div class="w-6 h-6 bg-blue-500 text-white rounded-full flex items-center justify-center text-xs font-semibold">2</div>
|
||||||
|
<div class="w-6 h-6 bg-gray-300 text-gray-600 rounded-full flex items-center justify-center text-xs font-semibold">3</div>
|
||||||
|
<div class="w-6 h-6 bg-gray-300 text-gray-600 rounded-full flex items-center justify-center text-xs font-semibold">4</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action="{{ route('feeds.store') }}" method="POST" class="space-y-6">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="name" class="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Feed Name
|
||||||
|
</label>
|
||||||
|
<input type="text"
|
||||||
|
id="name"
|
||||||
|
name="name"
|
||||||
|
value="{{ old('name') }}"
|
||||||
|
placeholder="My News Feed"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
required>
|
||||||
|
@error('name')
|
||||||
|
<p class="text-red-600 text-sm mt-1">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="url" class="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Feed URL
|
||||||
|
</label>
|
||||||
|
<input type="url"
|
||||||
|
id="url"
|
||||||
|
name="url"
|
||||||
|
value="{{ old('url') }}"
|
||||||
|
placeholder="https://example.com/rss.xml"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
required>
|
||||||
|
@error('url')
|
||||||
|
<p class="text-red-600 text-sm mt-1">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="type" class="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Feed Type
|
||||||
|
</label>
|
||||||
|
<select id="type"
|
||||||
|
name="type"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
required>
|
||||||
|
<option value="">Select feed type</option>
|
||||||
|
<option value="rss" {{ old('type') == 'rss' ? 'selected' : '' }}>RSS Feed</option>
|
||||||
|
<option value="website" {{ old('type') == 'website' ? 'selected' : '' }}>Website</option>
|
||||||
|
</select>
|
||||||
|
@error('type')
|
||||||
|
<p class="text-red-600 text-sm mt-1">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="language_id" class="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Language
|
||||||
|
</label>
|
||||||
|
<select id="language_id"
|
||||||
|
name="language_id"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
required>
|
||||||
|
<option value="">Select language</option>
|
||||||
|
@foreach(\App\Models\Language::orderBy('name')->get() as $language)
|
||||||
|
<option value="{{ $language->id }}" {{ old('language_id') == $language->id ? 'selected' : '' }}>
|
||||||
|
{{ $language->name }}
|
||||||
|
</option>
|
||||||
|
@endforeach
|
||||||
|
</select>
|
||||||
|
@error('language_id')
|
||||||
|
<p class="text-red-600 text-sm mt-1">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="description" class="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Description (Optional)
|
||||||
|
</label>
|
||||||
|
<textarea id="description"
|
||||||
|
name="description"
|
||||||
|
rows="3"
|
||||||
|
placeholder="Brief description of this feed"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent">{{ old('description') }}</textarea>
|
||||||
|
@error('description')
|
||||||
|
<p class="text-red-600 text-sm mt-1">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="hidden" name="is_active" value="1">
|
||||||
|
<input type="hidden" name="redirect_to" value="{{ route('onboarding.channel') }}">
|
||||||
|
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<a href="{{ route('onboarding.platform') }}"
|
||||||
|
class="px-4 py-2 text-gray-600 hover:text-gray-800 transition duration-200">
|
||||||
|
← Back
|
||||||
|
</a>
|
||||||
|
<button type="submit"
|
||||||
|
class="bg-blue-600 text-white py-2 px-6 rounded-md hover:bg-blue-700 transition duration-200">
|
||||||
|
Continue
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
86
resources/views/onboarding/platform.blade.php
Normal file
86
resources/views/onboarding/platform.blade.php
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="min-h-screen flex items-center justify-center bg-gray-50">
|
||||||
|
<div class="max-w-lg w-full bg-white rounded-lg shadow-md p-8">
|
||||||
|
<div class="text-center mb-8">
|
||||||
|
<h1 class="text-2xl font-bold text-gray-900 mb-2">Connect Your Lemmy Account</h1>
|
||||||
|
<p class="text-gray-600">
|
||||||
|
Enter your Lemmy instance details and login credentials
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<!-- Progress indicator -->
|
||||||
|
<div class="flex justify-center mt-6 space-x-2">
|
||||||
|
<div class="w-6 h-6 bg-blue-500 text-white rounded-full flex items-center justify-center text-xs font-semibold">1</div>
|
||||||
|
<div class="w-6 h-6 bg-gray-300 text-gray-600 rounded-full flex items-center justify-center text-xs font-semibold">2</div>
|
||||||
|
<div class="w-6 h-6 bg-gray-300 text-gray-600 rounded-full flex items-center justify-center text-xs font-semibold">3</div>
|
||||||
|
<div class="w-6 h-6 bg-gray-300 text-gray-600 rounded-full flex items-center justify-center text-xs font-semibold">4</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<form action="{{ route('platforms.store') }}" method="POST" class="space-y-6">
|
||||||
|
@csrf
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="instance_url" class="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Lemmy Instance URL
|
||||||
|
</label>
|
||||||
|
<input type="url"
|
||||||
|
id="instance_url"
|
||||||
|
name="instance_url"
|
||||||
|
value="{{ old('instance_url') }}"
|
||||||
|
placeholder="https://lemmy.world"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
required>
|
||||||
|
@error('instance_url')
|
||||||
|
<p class="text-red-600 text-sm mt-1">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="username" class="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Username
|
||||||
|
</label>
|
||||||
|
<input type="text"
|
||||||
|
id="username"
|
||||||
|
name="username"
|
||||||
|
value="{{ old('username') }}"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
required>
|
||||||
|
@error('username')
|
||||||
|
<p class="text-red-600 text-sm mt-1">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label for="password" class="block text-sm font-medium text-gray-700 mb-2">
|
||||||
|
Password
|
||||||
|
</label>
|
||||||
|
<input type="password"
|
||||||
|
id="password"
|
||||||
|
name="password"
|
||||||
|
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
||||||
|
required>
|
||||||
|
@error('password')
|
||||||
|
<p class="text-red-600 text-sm mt-1">{{ $message }}</p>
|
||||||
|
@enderror
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<input type="hidden" name="platform" value="lemmy">
|
||||||
|
<input type="hidden" name="is_active" value="1">
|
||||||
|
<input type="hidden" name="redirect_to" value="{{ route('onboarding.feed') }}">
|
||||||
|
|
||||||
|
<div class="flex justify-between">
|
||||||
|
<a href="{{ route('onboarding.index') }}"
|
||||||
|
class="px-4 py-2 text-gray-600 hover:text-gray-800 transition duration-200">
|
||||||
|
← Back
|
||||||
|
</a>
|
||||||
|
<button type="submit"
|
||||||
|
class="bg-blue-600 text-white py-2 px-6 rounded-md hover:bg-blue-700 transition duration-200">
|
||||||
|
Continue
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
40
resources/views/onboarding/welcome.blade.php
Normal file
40
resources/views/onboarding/welcome.blade.php
Normal file
|
|
@ -0,0 +1,40 @@
|
||||||
|
@extends('layouts.app')
|
||||||
|
|
||||||
|
@section('content')
|
||||||
|
<div class="min-h-screen flex items-center justify-center bg-gray-50">
|
||||||
|
<div class="max-w-md w-full bg-white rounded-lg shadow-md p-8">
|
||||||
|
<div class="text-center">
|
||||||
|
<h1 class="text-3xl font-bold text-gray-900 mb-2">Welcome to Lemmy Poster</h1>
|
||||||
|
<p class="text-gray-600 mb-8">
|
||||||
|
Let's get you set up! We'll help you configure your Lemmy account, add your first feed, and create a channel for posting.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="space-y-4">
|
||||||
|
<div class="flex items-center text-sm text-gray-600">
|
||||||
|
<div class="w-6 h-6 bg-blue-500 text-white rounded-full flex items-center justify-center mr-3 text-xs font-semibold">1</div>
|
||||||
|
<span>Connect your Lemmy account</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center text-sm text-gray-600">
|
||||||
|
<div class="w-6 h-6 bg-gray-300 text-gray-600 rounded-full flex items-center justify-center mr-3 text-xs font-semibold">2</div>
|
||||||
|
<span>Add your first feed</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center text-sm text-gray-600">
|
||||||
|
<div class="w-6 h-6 bg-gray-300 text-gray-600 rounded-full flex items-center justify-center mr-3 text-xs font-semibold">3</div>
|
||||||
|
<span>Configure a channel</span>
|
||||||
|
</div>
|
||||||
|
<div class="flex items-center text-sm text-gray-600">
|
||||||
|
<div class="w-6 h-6 bg-gray-300 text-gray-600 rounded-full flex items-center justify-center mr-3 text-xs font-semibold">4</div>
|
||||||
|
<span>You're ready to go!</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-8">
|
||||||
|
<a href="{{ route('onboarding.platform') }}"
|
||||||
|
class="w-full bg-blue-600 text-white py-3 px-4 rounded-md hover:bg-blue-700 transition duration-200 inline-block">
|
||||||
|
Get Started
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
@endsection
|
||||||
|
|
@ -2,8 +2,16 @@
|
||||||
|
|
||||||
use App\Http\Controllers\ArticlesController;
|
use App\Http\Controllers\ArticlesController;
|
||||||
use App\Http\Controllers\LogsController;
|
use App\Http\Controllers\LogsController;
|
||||||
|
use App\Http\Controllers\OnboardingController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
// Onboarding routes
|
||||||
|
Route::get('/', [OnboardingController::class, 'index'])->name('onboarding.index');
|
||||||
|
Route::get('/onboarding/platform', [OnboardingController::class, 'platform'])->name('onboarding.platform');
|
||||||
|
Route::get('/onboarding/feed', [OnboardingController::class, 'feed'])->name('onboarding.feed');
|
||||||
|
Route::get('/onboarding/channel', [OnboardingController::class, 'channel'])->name('onboarding.channel');
|
||||||
|
Route::get('/onboarding/complete', [OnboardingController::class, 'complete'])->name('onboarding.complete');
|
||||||
|
|
||||||
Route::get('/articles', ArticlesController::class)->name('articles');
|
Route::get('/articles', ArticlesController::class)->name('articles');
|
||||||
Route::get('/logs', LogsController::class)->name('logs');
|
Route::get('/logs', LogsController::class)->name('logs');
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue