Skip to content

Commit

Permalink
Add create feed modal and move controller to action
Browse files Browse the repository at this point in the history
  • Loading branch information
angristan committed Dec 21, 2024
1 parent 80e559c commit daa2311
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 66 deletions.
32 changes: 30 additions & 2 deletions app/Actions/Feed/CreateNewFeed.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,44 @@

use App\Models\Feed;
use AshAllenDesign\FaviconFetcher\Facades\Favicon;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Lorisleiva\Actions\Concerns\AsAction;

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
Expand All @@ -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
Expand Down
58 changes: 0 additions & 58 deletions app/Http/Controllers/FeedController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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.');
}
}
68 changes: 65 additions & 3 deletions resources/js/Pages/Feeds.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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,
Expand All @@ -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);
Expand Down Expand Up @@ -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 (
<form onSubmit={submit}>
<TextInput
type="text"
label="Feed URL"
placeholder="https://blog.cloudflare.com/rss/"
data-autofocus
value={data.feed_url}
onChange={(e) => setData('feed_url', e.target.value)}
/>
{errors.feed_url && <div>{errors.feed_url}</div>}
<Button mt="md" fullWidth type="submit" disabled={processing}>
Submit
</Button>
</form>
);
};

const NavBar = function Navbar({
user,
mainLinks,
Expand All @@ -191,6 +243,12 @@ const NavBar = function Navbar({
mainLinks: ReactNode[];
feedLinks: ReactNode[];
}) {
const openModal = () =>
modals.open({
title: 'Add a new feed',
children: <AddFeedModal />,
});

return (
<AppShell.Navbar>
<AppShell.Section pr="md" pl="md" pt="md">
Expand Down Expand Up @@ -220,7 +278,11 @@ const NavBar = function Navbar({
Feeds
</Text>
<Tooltip label="Create feed" withArrow position="right">
<ActionIcon variant="default" size={18}>
<ActionIcon
onClick={openModal}
variant="default"
size={18}
>
<IconPlus size={12} stroke={1.5} />
</ActionIcon>
</Tooltip>
Expand Down
7 changes: 5 additions & 2 deletions resources/js/app.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand All @@ -25,8 +26,10 @@ createInertiaApp({

root.render(
<MantineProvider>
<Notifications />
<App {...props} />
<ModalsProvider>
<Notifications />
<App {...props} />
</ModalsProvider>
</MantineProvider>,
);
},
Expand Down
3 changes: 2 additions & 1 deletion routes/web.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<?php

use App\Actions\Entry\ShowEntryPage;
use App\Actions\Feed\CreateNewFeed;
use App\Actions\Feed\RefreshFeedEntries;
use App\Actions\Feed\ShowFeedPage;
use App\Actions\Feed\ShowNewFeedPage;
Expand Down Expand Up @@ -35,7 +36,7 @@
Route::get('/feed/{feed}/entry/{entry}', ShowEntryPage::class)->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');
Expand Down

0 comments on commit daa2311

Please sign in to comment.