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',
|
||||
'url' => 'required|url|unique:feeds,url',
|
||||
'type' => 'required|in:website,rss',
|
||||
'language' => 'required|string|size:2',
|
||||
'language_id' => 'required|exists:languages,id',
|
||||
'description' => 'nullable|string',
|
||||
'is_active' => 'boolean'
|
||||
]);
|
||||
|
|
@ -39,6 +39,13 @@ public function store(Request $request): RedirectResponse
|
|||
|
||||
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')
|
||||
->with('success', 'Feed created successfully!');
|
||||
}
|
||||
|
|
@ -59,7 +66,7 @@ public function update(Request $request, Feed $feed): RedirectResponse
|
|||
'name' => 'required|string|max:255',
|
||||
'url' => 'required|url|unique:feeds,url,' . $feed->id,
|
||||
'type' => 'required|in:website,rss',
|
||||
'language' => 'required|string|size:2',
|
||||
'language_id' => 'required|exists:languages,id',
|
||||
'description' => 'nullable|string',
|
||||
'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;
|
||||
|
||||
use App\Models\PlatformAccount;
|
||||
use App\Models\PlatformInstance;
|
||||
use App\Enums\PlatformEnum;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Contracts\View\View;
|
||||
use Illuminate\Http\RedirectResponse;
|
||||
|
|
@ -31,6 +33,17 @@ public function store(Request $request): RedirectResponse
|
|||
'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);
|
||||
|
||||
// If this is the first account for this platform, make it active
|
||||
|
|
@ -38,6 +51,13 @@ public function store(Request $request): RedirectResponse
|
|||
$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')
|
||||
->with('success', 'Platform account created successfully!');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -34,13 +34,28 @@ public function store(Request $request): RedirectResponse
|
|||
$validated = $request->validate([
|
||||
'platform_instance_id' => 'required|exists:platform_instances,id',
|
||||
'name' => 'required|string|max:255',
|
||||
'display_name' => 'required|string|max:255',
|
||||
'channel_id' => 'required|string|max:255',
|
||||
'display_name' => 'nullable|string|max:255',
|
||||
'channel_id' => 'nullable|string|max:255',
|
||||
'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);
|
||||
|
||||
// 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')
|
||||
->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\LogsController;
|
||||
use App\Http\Controllers\OnboardingController;
|
||||
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('/logs', LogsController::class)->name('logs');
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue