From daa23119d74edeea0f5390d2e6aad6f3f246341c Mon Sep 17 00:00:00 2001 From: Stanislas Lange Date: Sat, 21 Dec 2024 12:18:35 +0100 Subject: [PATCH] Add create feed modal and move controller to action --- app/Actions/Feed/CreateNewFeed.php | 32 +++++++++++- app/Http/Controllers/FeedController.php | 58 --------------------- resources/js/Pages/Feeds.tsx | 68 +++++++++++++++++++++++-- resources/js/app.tsx | 7 ++- routes/web.php | 3 +- 5 files changed, 102 insertions(+), 66 deletions(-) diff --git a/app/Actions/Feed/CreateNewFeed.php b/app/Actions/Feed/CreateNewFeed.php index bce1722c..d5727b43 100644 --- a/app/Actions/Feed/CreateNewFeed.php +++ b/app/Actions/Feed/CreateNewFeed.php @@ -4,6 +4,7 @@ use App\Models\Feed; use AshAllenDesign\FaviconFetcher\Facades\Favicon; +use Illuminate\Http\Request; use Illuminate\Support\Facades\Log; use Lorisleiva\Actions\Concerns\AsAction; @@ -11,11 +12,36 @@ class CreateNewFeed { use AsAction; + public function rules(): array + { + return [ + 'feed_url' => ['required', 'max:255', 'url'], + ]; + } + + public function getValidationMessages(): array + { + return [ + 'feed_url.required' => 'Please enter a feed URL', + 'feed_url.url' => 'Please enter a valid URL', + 'feed_url.max' => 'Please enter a URL that is less than 255 characters', + ]; + } + + public function asController(Request $request) + { + $this->handle($request->feed_url); + + return redirect()->route('feeds.index'); + } + public function handle(string $feed_url) { // Skip if feed already exists if (Feed::where('feed_url', $feed_url)->exists()) { - return; + return redirect()->route('feeds.index')->withErrors([ + 'feed_url' => 'Feed already exists', + ]); } // TODO fetch limit @@ -36,7 +62,9 @@ public function handle(string $feed_url) // 'feed_url' => $error, // ]); - return; + return redirect()->route('feeds.index')->withErrors([ + 'feed_url' => $error, + ]); } // Handle feeds without site link such as https://aggregate.stitcher.io/rss diff --git a/app/Http/Controllers/FeedController.php b/app/Http/Controllers/FeedController.php index 8eb23292..6a50d136 100644 --- a/app/Http/Controllers/FeedController.php +++ b/app/Http/Controllers/FeedController.php @@ -2,10 +2,8 @@ namespace App\Http\Controllers; -use App\Http\Requests\StoreFeedRequest; use App\Models\Entry; use App\Models\Feed; -use AshAllenDesign\FaviconFetcher\Facades\Favicon; use Illuminate\Http\Request; use Inertia\Inertia; @@ -40,60 +38,4 @@ public function index(Request $request): \Inertia\Response 'currententry' => Inertia::lazy(fn () => Entry::whereId($request->query('entry'))->first()), ]); } - - /** - * Store a newly created resource in storage. - */ - public function store(StoreFeedRequest $request): \Illuminate\Http\RedirectResponse - { - $feed_url = ''; - $feed_url = $request->validated()['feed_url']; - - // TODO fetch limit - $crawledFeed = \Feeds::make(feedUrl: $feed_url); - if ($crawledFeed->error()) { - $error = ''; - if (is_array($crawledFeed->error())) { - $error = implode(', ', $crawledFeed->error()); - } else { - $error = $crawledFeed->error(); - } - // "cURL error 3: " -> "cURL error 3" - // idk why it adds a colon at the end - $error = rtrim($error, ': '); - - return redirect()->back()->withErrors([ - 'feed_url' => $error, - ]); - } - - // Handle feeds without site link such as https://aggregate.stitcher.io/rss - $site_url = $crawledFeed->get_link() ?? $feed_url; - - // TODO fix + cache/store + refresh - $favicon_url = Favicon::withFallback('favicon-kit')->fetch($site_url)?->getFaviconUrl(); - - $feed = Feed::create([ - 'name' => $crawledFeed->get_title(), - 'feed_url' => $feed_url, - 'site_url' => $site_url, - 'favicon_url' => $favicon_url, - ]); - - // TODO single insert - $entries = $crawledFeed->get_items(); - foreach ($entries as $entry) { - $feed->entries()->create([ - 'title' => $entry->get_title(), - 'url' => $entry->get_permalink(), - 'content' => $entry->get_content(), - 'published_at' => $entry->get_date('Y-m-d H:i:s'), - ]); - } - - return redirect()->route('feed.entries', $feed) - // TODO success message - // https://inertiajs.com/shared-data#flash-messages - ->with('success', 'Feed added successfully.'); - } } diff --git a/resources/js/Pages/Feeds.tsx b/resources/js/Pages/Feeds.tsx index 64535f2d..0a4bff10 100644 --- a/resources/js/Pages/Feeds.tsx +++ b/resources/js/Pages/Feeds.tsx @@ -5,12 +5,13 @@ import ApplicationLogo from '@/Components/ApplicationLogo'; import AuthenticatedLayout from '@/Layouts/AuthenticatedLayout'; import { User } from '@/types'; import { Split } from '@gfazioli/mantine-split-pane'; -import { router, usePage } from '@inertiajs/react'; +import { router, useForm, usePage } from '@inertiajs/react'; import { ActionIcon, AppShell, Badge, Burger, + Button, Card, Code, Flex, @@ -26,6 +27,8 @@ import { UnstyledButton, } from '@mantine/core'; import { useDisclosure } from '@mantine/hooks'; +import { modals } from '@mantine/modals'; +import { notifications } from '@mantine/notifications'; import { IconBook, IconCheckbox, @@ -36,7 +39,7 @@ import { import dayjs from 'dayjs'; import calendar from 'dayjs/plugin/calendar'; import utc from 'dayjs/plugin/utc'; -import { ReactNode, useEffect, useRef } from 'react'; +import { FormEventHandler, ReactNode, useEffect, useRef } from 'react'; dayjs.extend(calendar); dayjs.extend(utc); @@ -182,6 +185,55 @@ const Main = function Main({ ); }; +const AddFeedModal = function AddFeedModal() { + const { data, setData, post, errors, processing } = useForm({ + feed_url: '', + }); + + const submit: FormEventHandler = (e) => { + e.preventDefault(); + + post(route('feed.store'), { + onSuccess: () => { + notifications.show({ + title: 'Feed added', + message: 'The feed has been added', + color: 'green', + withBorder: true, + }); + + modals.closeAll(); + }, + onError: (errors) => { + setData('feed_url', ''); + notifications.show({ + title: 'Failed to add feed', + message: errors.feed_url, + color: 'red', + withBorder: true, + }); + }, + }); + }; + + return ( +
+ setData('feed_url', e.target.value)} + /> + {errors.feed_url &&
{errors.feed_url}
} + + + ); +}; + const NavBar = function Navbar({ user, mainLinks, @@ -191,6 +243,12 @@ const NavBar = function Navbar({ mainLinks: ReactNode[]; feedLinks: ReactNode[]; }) { + const openModal = () => + modals.open({ + title: 'Add a new feed', + children: , + }); + return ( @@ -220,7 +278,11 @@ const NavBar = function Navbar({ Feeds - + diff --git a/resources/js/app.tsx b/resources/js/app.tsx index e8765f0f..5f5683fa 100644 --- a/resources/js/app.tsx +++ b/resources/js/app.tsx @@ -7,6 +7,7 @@ import '../css/app.css'; import './bootstrap'; import { createInertiaApp } from '@inertiajs/react'; import { MantineProvider } from '@mantine/core'; +import { ModalsProvider } from '@mantine/modals'; import { Notifications } from '@mantine/notifications'; import { resolvePageComponent } from 'laravel-vite-plugin/inertia-helpers'; import { createRoot } from 'react-dom/client'; @@ -25,8 +26,10 @@ createInertiaApp({ root.render( - - + + + + , ); }, diff --git a/routes/web.php b/routes/web.php index 5cf5e358..f6511c1d 100644 --- a/routes/web.php +++ b/routes/web.php @@ -1,6 +1,7 @@ name('feed.entry')->whereNumber('feed')->whereNumber('entry'); Route::post('/feed/{feed}/refresh', RefreshFeedEntries::class)->name('feed.refresh')->whereNumber('feed'); Route::get('/feed/new', ShowNewFeedPage::class)->name('feed.create'); - Route::post('/feed', [FeedController::class, 'store'])->name('feed.store'); + Route::post('/feed', CreateNewFeed::class)->name('feed.store'); Route::get('/import', [ImportOPML::class, 'index'])->name('import.index'); Route::post('/import', [ImportOPML::class, 'store'])->name('import.store');