fedi-feed-router/resources/views/livewire/onboarding.blade.php

547 lines
31 KiB
PHP
Raw Normal View History

2026-01-23 00:56:01 +01:00
<div class="w-full max-w-2xl px-4 sm:px-6 lg:px-8">
<div class="w-full bg-white rounded-2xl shadow-xl p-8">
{{-- Step 1: Welcome --}}
@if ($step === 1)
<div class="text-center">
<h1 class="text-3xl font-bold text-gray-900 mb-2">Welcome to FFR</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 text-left">
<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>Create a route</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">5</div>
<span>You're ready to go!</span>
</div>
</div>
<div class="mt-8">
<button
wire:click="nextStep"
class="w-full bg-blue-600 text-white py-3 px-4 rounded-md hover:bg-blue-700 transition duration-200"
>
Get Started
</button>
</div>
</div>
@endif
{{-- Step 2: Platform Account --}}
@if ($step === 2)
<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">
{{ $existingAccount ? 'Your connected Lemmy account' : '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>
2026-01-23 00:56:01 +01:00
@if (!empty($formErrors['general']))
<div class="p-3 bg-red-50 border border-red-200 rounded-md mt-6">
2026-01-23 00:56:01 +01:00
<p class="text-red-600 text-sm">{{ $formErrors['general'] }}</p>
</div>
@endif
@if ($existingAccount)
{{-- Account Card --}}
<div class="mt-8">
<div class="bg-green-50 border border-green-200 rounded-lg p-6 text-left">
<div class="flex items-start justify-between">
<div class="flex items-center">
<div class="w-12 h-12 bg-green-500 text-white rounded-full flex items-center justify-center">
<svg class="w-6 h-6" 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>
<div class="ml-4">
<h3 class="text-lg font-semibold text-gray-900">Account Connected</h3>
<div class="text-sm text-gray-600 mt-1">
<p><strong>Username:</strong> {{ $existingAccount['username'] }}</p>
<p><strong>Instance:</strong> {{ str_replace('https://', '', $existingAccount['instance_url']) }}</p>
</div>
</div>
</div>
<button
wire:click="deleteAccount"
wire:confirm="Are you sure you want to remove this account? You will need to re-enter your credentials."
class="text-red-600 hover:text-red-800 p-2"
title="Remove account"
>
<svg class="w-5 h-5" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
</svg>
</button>
</div>
</div>
<div class="flex justify-between mt-6">
<button wire:click="previousStep" class="px-4 py-2 text-gray-600 hover:text-gray-800 transition duration-200">
Back
</button>
<button
wire:click="continueWithExistingAccount"
class="bg-blue-600 text-white py-2 px-6 rounded-md hover:bg-blue-700 transition duration-200"
>
Continue
</button>
</div>
</div>
@else
{{-- Login Form --}}
<form wire:submit="createPlatformAccount" class="space-y-6 mt-8 text-left">
<div>
<label for="instanceUrl" class="block text-sm font-medium text-gray-700 mb-2">
Lemmy Instance Domain
</label>
<input
type="text"
id="instanceUrl"
wire:model="instanceUrl"
placeholder="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"
required
>
<p class="text-sm text-gray-500 mt-1">Enter just the domain name (e.g., lemmy.world, belgae.social)</p>
@error('instanceUrl') <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"
wire:model="username"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
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"
wire:model="password"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
required
>
@error('password') <p class="text-red-600 text-sm mt-1">{{ $message }}</p> @enderror
</div>
<div class="flex justify-between">
<button type="button" wire:click="previousStep" class="px-4 py-2 text-gray-600 hover:text-gray-800 transition duration-200">
Back
</button>
<button
type="submit"
@disabled($isLoading)
class="bg-blue-600 text-white py-2 px-6 rounded-md hover:bg-blue-700 transition duration-200 disabled:opacity-50"
>
{{ $isLoading ? 'Connecting...' : 'Continue' }}
</button>
</div>
</form>
@endif
</div>
@endif
{{-- Step 3: Feed --}}
@if ($step === 3)
<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">
Choose from our supported news providers 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>
<form wire:submit="createFeed" class="space-y-6 mt-8 text-left">
2026-01-23 00:56:01 +01:00
@if (!empty($formErrors['general']))
<div class="p-3 bg-red-50 border border-red-200 rounded-md">
2026-01-23 00:56:01 +01:00
<p class="text-red-600 text-sm">{{ $formErrors['general'] }}</p>
</div>
@endif
<div>
<label for="feedName" class="block text-sm font-medium text-gray-700 mb-2">
Feed Name
</label>
<input
type="text"
id="feedName"
wire:model="feedName"
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"
required
>
@error('feedName') <p class="text-red-600 text-sm mt-1">{{ $message }}</p> @enderror
</div>
<div>
<label for="feedProvider" class="block text-sm font-medium text-gray-700 mb-2">
News Provider
</label>
<select
id="feedProvider"
wire:model="feedProvider"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
required
>
<option value="">Select news provider</option>
@foreach ($feedProviders as $provider)
<option value="{{ $provider['code'] }}">{{ $provider['name'] }}</option>
@endforeach
</select>
@error('feedProvider') <p class="text-red-600 text-sm mt-1">{{ $message }}</p> @enderror
</div>
<div>
<label for="feedLanguageId" class="block text-sm font-medium text-gray-700 mb-2">
Language
</label>
<select
id="feedLanguageId"
wire:model="feedLanguageId"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
required
>
<option value="">Select language</option>
@foreach ($languages as $language)
<option value="{{ $language->id }}">{{ $language->name }}</option>
@endforeach
</select>
@error('feedLanguageId') <p class="text-red-600 text-sm mt-1">{{ $message }}</p> @enderror
</div>
<div>
<label for="feedDescription" class="block text-sm font-medium text-gray-700 mb-2">
Description (Optional)
</label>
<textarea
id="feedDescription"
wire:model="feedDescription"
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"
></textarea>
@error('feedDescription') <p class="text-red-600 text-sm mt-1">{{ $message }}</p> @enderror
</div>
<div class="flex justify-between">
<button type="button" wire:click="previousStep" class="px-4 py-2 text-gray-600 hover:text-gray-800 transition duration-200">
Back
</button>
<button
type="submit"
@disabled($isLoading)
class="bg-blue-600 text-white py-2 px-6 rounded-md hover:bg-blue-700 transition duration-200 disabled:opacity-50"
>
{{ $isLoading ? 'Creating...' : 'Continue' }}
</button>
</div>
</form>
</div>
@endif
{{-- Step 4: Channel --}}
@if ($step === 4)
<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>
<form wire:submit="createChannel" class="space-y-6 mt-8 text-left">
2026-01-23 00:56:01 +01:00
@if (!empty($formErrors['general']))
<div class="p-3 bg-red-50 border border-red-200 rounded-md">
2026-01-23 00:56:01 +01:00
<p class="text-red-600 text-sm">{{ $formErrors['general'] }}</p>
</div>
@endif
<div>
<label for="channelName" class="block text-sm font-medium text-gray-700 mb-2">
Community Name
</label>
<input
type="text"
id="channelName"
wire:model="channelName"
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"
required
>
<p class="text-sm text-gray-500 mt-1">Enter the community name (without the @ or instance)</p>
@error('channelName') <p class="text-red-600 text-sm mt-1">{{ $message }}</p> @enderror
</div>
<div>
<label for="platformInstanceId" class="block text-sm font-medium text-gray-700 mb-2">
Platform Instance
</label>
<select
id="platformInstanceId"
wire:model="platformInstanceId"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
required
>
<option value="">Select platform instance</option>
@foreach ($platformInstances as $instance)
<option value="{{ $instance->id }}">{{ $instance->name }} ({{ $instance->url }})</option>
@endforeach
</select>
@error('platformInstanceId') <p class="text-red-600 text-sm mt-1">{{ $message }}</p> @enderror
</div>
<div>
<label for="channelLanguageId" class="block text-sm font-medium text-gray-700 mb-2">
Language
</label>
<select
id="channelLanguageId"
wire:model="channelLanguageId"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
required
>
<option value="">Select language</option>
@foreach ($languages as $language)
<option value="{{ $language->id }}">{{ $language->name }}</option>
@endforeach
</select>
@error('channelLanguageId') <p class="text-red-600 text-sm mt-1">{{ $message }}</p> @enderror
</div>
<div>
<label for="channelDescription" class="block text-sm font-medium text-gray-700 mb-2">
Description (Optional)
</label>
<textarea
id="channelDescription"
wire:model="channelDescription"
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"
></textarea>
@error('channelDescription') <p class="text-red-600 text-sm mt-1">{{ $message }}</p> @enderror
</div>
<div class="flex justify-between">
<button type="button" wire:click="previousStep" class="px-4 py-2 text-gray-600 hover:text-gray-800 transition duration-200">
Back
</button>
<button
type="submit"
@disabled($isLoading)
class="bg-blue-600 text-white py-2 px-6 rounded-md hover:bg-blue-700 transition duration-200 disabled:opacity-50"
>
{{ $isLoading ? 'Creating...' : 'Continue' }}
</button>
</div>
</form>
</div>
@endif
{{-- Step 5: Route --}}
@if ($step === 5)
<div class="text-center mb-8">
<h1 class="text-2xl font-bold text-gray-900 mb-2">Create Your First Route</h1>
<p class="text-gray-600">
Connect your feed to a channel by creating a route. This tells FFR which articles to post where.
</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-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">4</div>
<div class="w-6 h-6 bg-gray-300 text-gray-600 rounded-full flex items-center justify-center text-xs font-semibold">5</div>
</div>
<form wire:submit="createRoute" class="space-y-6 mt-8 text-left">
2026-01-23 00:56:01 +01:00
@if (!empty($formErrors['general']))
<div class="p-3 bg-red-50 border border-red-200 rounded-md">
2026-01-23 00:56:01 +01:00
<p class="text-red-600 text-sm">{{ $formErrors['general'] }}</p>
</div>
@endif
<div>
<label for="routeFeedId" class="block text-sm font-medium text-gray-700 mb-2">
Select Feed
</label>
<select
id="routeFeedId"
wire:model="routeFeedId"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
required
>
<option value="">Select a feed</option>
@foreach ($feeds as $feed)
<option value="{{ $feed->id }}">{{ $feed->name }}</option>
@endforeach
</select>
@error('routeFeedId') <p class="text-red-600 text-sm mt-1">{{ $message }}</p> @enderror
</div>
<div>
<label for="routeChannelId" class="block text-sm font-medium text-gray-700 mb-2">
Select Channel
</label>
<select
id="routeChannelId"
wire:model="routeChannelId"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
required
>
<option value="">Select a channel</option>
@foreach ($channels as $channel)
<option value="{{ $channel->id }}">{{ $channel->display_name ?? $channel->name }}</option>
@endforeach
</select>
@if ($channels->isEmpty())
<p class="text-sm text-gray-500 mt-1">
No channels available. Please create a channel first.
</p>
@endif
@error('routeChannelId') <p class="text-red-600 text-sm mt-1">{{ $message }}</p> @enderror
</div>
<div>
<label for="routePriority" class="block text-sm font-medium text-gray-700 mb-2">
Priority (1-100)
</label>
<input
type="number"
id="routePriority"
wire:model="routePriority"
min="1"
max="100"
placeholder="50"
class="w-full px-3 py-2 border border-gray-300 rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500"
>
<p class="text-sm text-gray-500 mt-1">
Higher priority routes are processed first (default: 50)
</p>
@error('routePriority') <p class="text-red-600 text-sm mt-1">{{ $message }}</p> @enderror
</div>
<div class="flex justify-between">
<button type="button" wire:click="previousStep" class="px-4 py-2 text-gray-600 hover:text-gray-800 transition duration-200">
Back
</button>
<button
type="submit"
@disabled($isLoading || $channels->isEmpty())
class="bg-blue-600 text-white py-2 px-6 rounded-md hover:bg-blue-700 transition duration-200 disabled:opacity-50"
>
{{ $isLoading ? 'Creating...' : 'Continue' }}
</button>
</div>
</form>
</div>
@endif
{{-- Step 6: Complete --}}
@if ($step === 6)
<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 FFR. 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 other 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">
You can add more feeds, channels, and configure settings from the dashboard.
</p>
</div>
</div>
<div class="space-y-3">
<button
wire:click="completeOnboarding"
@disabled($isLoading)
class="w-full bg-blue-600 text-white py-3 px-4 rounded-md hover:bg-blue-700 transition duration-200 disabled:opacity-50"
>
{{ $isLoading ? 'Finishing...' : 'Go to Dashboard' }}
</button>
<div class="text-sm text-gray-500">
<a href="{{ route('articles') }}" class="hover:text-blue-600">View Articles</a>
<a href="{{ route('feeds') }}" class="hover:text-blue-600">Manage Feeds</a>
<a href="{{ route('settings') }}" class="hover:text-blue-600">Settings</a>
</div>
</div>
</div>
@endif
</div>
</div>