feat: Implement news listing page with search, filter, and pagination, and update news table schema.
This commit is contained in:
parent
2eaed7bb27
commit
861aff3c78
70
app/Http/Controllers/Home/NewsController.php
Normal file
70
app/Http/Controllers/Home/NewsController.php
Normal file
@ -0,0 +1,70 @@
|
||||
<?php
|
||||
|
||||
namespace App\Http\Controllers\Home;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Category;
|
||||
use App\Models\News;
|
||||
use Carbon\Carbon;
|
||||
use Illuminate\Http\Request;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class NewsController extends Controller
|
||||
{
|
||||
public function index(Request $request)
|
||||
{
|
||||
$search = $request->get('search');
|
||||
$category = $request->get('category');
|
||||
|
||||
$news = News::with(['author', 'categories'])
|
||||
->published()
|
||||
->when($search, function ($query, $search) {
|
||||
$query->where(function ($q) use ($search) {
|
||||
$q->where('title', 'like', "%{$search}%")
|
||||
->orWhere('content', 'like', "%{$search}%")
|
||||
->orWhere('excerpt', 'like', "%{$search}%");
|
||||
});
|
||||
})
|
||||
->when($category, function ($query, $category) {
|
||||
if ($category !== 'all') {
|
||||
$query->whereHas('categories', function ($q) use ($category) {
|
||||
$q->where('categories.id', $category);
|
||||
});
|
||||
}
|
||||
})
|
||||
->orderBy('published_at', 'desc')
|
||||
->paginate(12)
|
||||
->withQueryString()
|
||||
->through(function ($news) {
|
||||
$news->thumbnail = $news->getFirstMediaUrl('news');
|
||||
$news->formatted_published_at = Carbon::parse($news->published_at)
|
||||
->locale('id')
|
||||
->translatedFormat('l, d F Y');
|
||||
|
||||
return $news;
|
||||
});
|
||||
|
||||
$mostViewedNews = News::with(['author', 'categories'])
|
||||
->published()
|
||||
->orderBy('views', 'desc')
|
||||
->take(4)
|
||||
->get()
|
||||
->map(function ($news) {
|
||||
$news->thumbnail = $news->getFirstMediaUrl('news');
|
||||
$news->formatted_published_at = Carbon::parse($news->published_at)
|
||||
->locale('id')
|
||||
->translatedFormat('l, d F Y');
|
||||
|
||||
return $news;
|
||||
});
|
||||
|
||||
return Inertia::render('home/news/pages/Index', [
|
||||
'pageTitle' => 'Berita',
|
||||
'pageDescription' => 'Dapatkan informasi terbaru, artikel, dan update berita terkini tentang Purwakarta.',
|
||||
'categories' => Category::active()->get(),
|
||||
'news' => $news,
|
||||
'mostViewedNews' => $mostViewedNews,
|
||||
'filters' => request()->only(['search', 'category']),
|
||||
]);
|
||||
}
|
||||
}
|
||||
@ -21,7 +21,7 @@ public function up(): void
|
||||
$table->text('content');
|
||||
$table->text('excerpt');
|
||||
$table->string('link', 50)->nullable();
|
||||
$table->unsignedInteger('view')->default(0);
|
||||
$table->unsignedInteger('views')->default(0);
|
||||
$table->enum('status', [NewsStatus::values()])->comment(NewsStatus::comment());
|
||||
$table->timestamp('published_at')->nullable();
|
||||
$table->timestamp('created_at')->useCurrent();
|
||||
|
||||
43
resources/js/pages/home/components/Headline.jsx
Normal file
43
resources/js/pages/home/components/Headline.jsx
Normal file
@ -0,0 +1,43 @@
|
||||
import Container from '@mui/material/Container';
|
||||
import Stack from '@mui/material/Stack';
|
||||
import Typography from '@mui/material/Typography';
|
||||
|
||||
export default function Headline({ pageTitle, pageDescription }) {
|
||||
return (
|
||||
|
||||
<Container
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
alignItems: 'center'
|
||||
}}
|
||||
>
|
||||
<Stack
|
||||
spacing={2}
|
||||
useFlexGap
|
||||
sx={{ alignItems: 'center', width: { xs: '100%', sm: '70%' } }}
|
||||
>
|
||||
<Typography
|
||||
variant="h1"
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: { xs: 'column', sm: 'row' },
|
||||
alignItems: 'center',
|
||||
fontSize: 'clamp(3rem, 10vw, 3.5rem)',
|
||||
}}
|
||||
>
|
||||
{pageTitle}
|
||||
</Typography>
|
||||
<Typography
|
||||
sx={{
|
||||
textAlign: 'center',
|
||||
color: 'text.secondary',
|
||||
width: { sm: '100%', md: '80%' },
|
||||
}}
|
||||
>
|
||||
{pageDescription}
|
||||
</Typography>
|
||||
</Stack>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
@ -13,6 +13,7 @@ import MenuIcon from '@mui/icons-material/Menu';
|
||||
import CloseRoundedIcon from '@mui/icons-material/CloseRounded';
|
||||
import ColorModeIconDropdown from '../../theme/ColorModeIconDropdown';
|
||||
import Logo from './Logo';
|
||||
import { router } from '@inertiajs/react';
|
||||
|
||||
const StyledToolbar = styled(Toolbar)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
@ -53,8 +54,13 @@ export default function Navbar() {
|
||||
<Box sx={{ flexGrow: 1, display: 'flex', alignItems: 'center', px: 0 }}>
|
||||
<Logo />
|
||||
<Box sx={{ display: { xs: 'none', md: 'flex' } }}>
|
||||
<Button variant="text" color="info" size="small">
|
||||
Features
|
||||
<Button
|
||||
variant="text"
|
||||
color="info"
|
||||
size="small"
|
||||
onClick={() => router.get('/news')}
|
||||
>
|
||||
Berita
|
||||
</Button>
|
||||
<Button variant="text" color="info" size="small">
|
||||
Testimonials
|
||||
|
||||
33
resources/js/pages/home/news/components/Author.jsx
Normal file
33
resources/js/pages/home/news/components/Author.jsx
Normal file
@ -0,0 +1,33 @@
|
||||
import { Avatar, Box, Typography } from "@mui/material";
|
||||
|
||||
export default function Author({ author, publishedAt }) {
|
||||
return (
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
gap: 2,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'space-between',
|
||||
padding: '16px',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{ display: 'flex', flexDirection: 'row', gap: 1, alignItems: 'center' }}
|
||||
>
|
||||
<Avatar
|
||||
alt={author}
|
||||
sx={{ width: 24, height: 24 }}
|
||||
>
|
||||
{author.charAt(0).toUpperCase()}
|
||||
</Avatar>
|
||||
<Typography variant="caption">
|
||||
{author}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Typography variant="caption">
|
||||
{publishedAt}
|
||||
</Typography>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
272
resources/js/pages/home/news/components/MainContent.jsx
Normal file
272
resources/js/pages/home/news/components/MainContent.jsx
Normal file
@ -0,0 +1,272 @@
|
||||
import { router } from '@inertiajs/react';
|
||||
import Box from '@mui/material/Box';
|
||||
import Card from '@mui/material/Card';
|
||||
import CardContent from '@mui/material/CardContent';
|
||||
import CardMedia from '@mui/material/CardMedia';
|
||||
import Chip from '@mui/material/Chip';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import Headline from '../../components/Headline';
|
||||
import { Pagination } from '@mui/material';
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import Author from './Author';
|
||||
import { DotLottieReact } from '@lottiefiles/dotlottie-react';
|
||||
import { Search } from './Search';
|
||||
|
||||
const StyledCard = styled(Card)(({ theme }) => ({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
padding: 0,
|
||||
height: '100%',
|
||||
backgroundColor: (theme.vars || theme).palette.background.paper,
|
||||
'&:hover': {
|
||||
backgroundColor: 'transparent',
|
||||
cursor: 'pointer',
|
||||
},
|
||||
'&:focus-visible': {
|
||||
outline: '3px solid',
|
||||
outlineColor: 'hsla(210, 98%, 48%, 0.5)',
|
||||
outlineOffset: '2px',
|
||||
},
|
||||
}));
|
||||
|
||||
const StyledCardContent = styled(CardContent)({
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
gap: 4,
|
||||
padding: 16,
|
||||
flexGrow: 1,
|
||||
'&:last-child': {
|
||||
paddingBottom: 16,
|
||||
},
|
||||
});
|
||||
|
||||
const StyledTypography = styled(Typography)({
|
||||
display: '-webkit-box',
|
||||
WebkitBoxOrient: 'vertical',
|
||||
WebkitLineClamp: 2,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
});
|
||||
|
||||
export default function MainContent({ pageTitle, pageDescription, categories, news, filters }) {
|
||||
const [selectedCategory, setSelectedCategory] = useState(filters?.category || 'all');
|
||||
const [search, setSearch] = useState(filters?.search || '');
|
||||
const [focusedCardIndex, setFocusedCardIndex] = useState(null);
|
||||
const searchDebounceRef = useRef(null);
|
||||
|
||||
const handleFocus = (index) => {
|
||||
setFocusedCardIndex(index);
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (search === (filters?.search || '')) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (searchDebounceRef.current) {
|
||||
clearTimeout(searchDebounceRef.current);
|
||||
}
|
||||
|
||||
searchDebounceRef.current = setTimeout(() => {
|
||||
router.get(window.location.pathname, {
|
||||
category: selectedCategory,
|
||||
search: search
|
||||
}, {
|
||||
preserveState: true,
|
||||
preserveScroll: true,
|
||||
replace: true,
|
||||
});
|
||||
}, 500);
|
||||
|
||||
return () => {
|
||||
if (searchDebounceRef.current) {
|
||||
clearTimeout(searchDebounceRef.current);
|
||||
}
|
||||
};
|
||||
}, [search]);
|
||||
|
||||
const handleBlur = () => {
|
||||
setFocusedCardIndex(null);
|
||||
};
|
||||
|
||||
const handleCategory = (category) => {
|
||||
if (searchDebounceRef.current) {
|
||||
clearTimeout(searchDebounceRef.current);
|
||||
}
|
||||
|
||||
setSelectedCategory(category);
|
||||
router.get(window.location.pathname, {
|
||||
category: category,
|
||||
search: search
|
||||
}, {
|
||||
preserveState: true,
|
||||
preserveScroll: true
|
||||
});
|
||||
};
|
||||
|
||||
const handleSearch = () => {
|
||||
if (searchDebounceRef.current) {
|
||||
clearTimeout(searchDebounceRef.current);
|
||||
}
|
||||
|
||||
router.get(window.location.pathname, {
|
||||
category: selectedCategory,
|
||||
search: search
|
||||
}, {
|
||||
preserveState: true,
|
||||
preserveScroll: true
|
||||
});
|
||||
};
|
||||
|
||||
const handlePageChange = (event, value) => {
|
||||
router.get(window.location.pathname, {
|
||||
page: value,
|
||||
category: selectedCategory,
|
||||
search: search
|
||||
}, {
|
||||
preserveState: true,
|
||||
preserveScroll: true,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 4 }}>
|
||||
<div>
|
||||
<Headline pageTitle={pageTitle} pageDescription={pageDescription} />
|
||||
</div>
|
||||
<Box
|
||||
sx={{
|
||||
display: { xs: 'flex', sm: 'none' },
|
||||
flexDirection: 'row',
|
||||
gap: 1,
|
||||
width: { xs: '100%', md: 'fit-content' },
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
<Search value={search} onChange={(e) => setSearch(e.target.value)} onSearch={handleSearch} />
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: { xs: 'column-reverse', md: 'row' },
|
||||
width: '100%',
|
||||
justifyContent: 'space-between',
|
||||
alignItems: { xs: 'start', md: 'center' },
|
||||
gap: 4,
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'inline-flex',
|
||||
flexDirection: 'row',
|
||||
gap: 1,
|
||||
overflow: 'auto',
|
||||
scrollbarWidth: 'none',
|
||||
'&::-webkit-scrollbar': { display: 'none' },
|
||||
}}
|
||||
>
|
||||
<Chip
|
||||
onClick={() => handleCategory('all')}
|
||||
size="medium"
|
||||
label="Semua"
|
||||
sx={{
|
||||
backgroundColor: selectedCategory === 'all' ? '' : 'transparent',
|
||||
}}
|
||||
/>
|
||||
{categories.map((category, index) =>
|
||||
<Chip
|
||||
onClick={() => handleCategory(category.id)}
|
||||
size="medium"
|
||||
label={category.name}
|
||||
key={index}
|
||||
sx={{
|
||||
backgroundColor: selectedCategory === category.id ? '' : 'transparent',
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
display: { xs: 'none', sm: 'flex' },
|
||||
flexDirection: 'row',
|
||||
gap: 1,
|
||||
width: { xs: '100%', md: 'fit-content' },
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
<Search value={search} onChange={(e) => setSearch(e.target.value)} onSearch={handleSearch} />
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Grid container spacing={2} columns={12}>
|
||||
{news.data.map((news, index) =>
|
||||
<Grid size={{ xs: 12, md: 6 }} key={index}>
|
||||
<StyledCard
|
||||
variant="outlined"
|
||||
onFocus={() => handleFocus(news.id)}
|
||||
onBlur={handleBlur}
|
||||
tabIndex={0}
|
||||
className={focusedCardIndex === news.id ? 'Mui-focused' : ''}
|
||||
>
|
||||
<CardMedia
|
||||
component="img"
|
||||
alt={news.title}
|
||||
image={news.thumbnail}
|
||||
sx={{
|
||||
aspectRatio: '16 / 9',
|
||||
borderBottom: '1px solid',
|
||||
borderColor: 'divider',
|
||||
}}
|
||||
/>
|
||||
<StyledCardContent>
|
||||
<Typography gutterBottom variant="caption" component="div">
|
||||
{news.categories.map((category, index) =>
|
||||
<Chip label={category.name} color="success" variant="outlined" sx={{ mr: 1 }} key={index} />
|
||||
)}
|
||||
</Typography>
|
||||
<Typography gutterBottom variant="h6" component="div">
|
||||
{news.title}
|
||||
</Typography>
|
||||
<StyledTypography
|
||||
variant="body2"
|
||||
color="text.secondary"
|
||||
gutterBottom
|
||||
dangerouslySetInnerHTML={{ __html: news.excerpt }}
|
||||
/>
|
||||
</StyledCardContent>
|
||||
<Author author={news.author?.name} publishedAt={news.formatted_published_at} />
|
||||
</StyledCard>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
|
||||
{news.data.length > 0 ? (
|
||||
<Box sx={{ display: 'flex', flexDirection: 'row', py: 4 }}>
|
||||
<Pagination
|
||||
count={news.last_page}
|
||||
showFirstButton
|
||||
showLastButton
|
||||
page={news.current_page}
|
||||
onChange={handlePageChange}
|
||||
/>
|
||||
</Box>
|
||||
) : (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', flexDirection: 'column' }}>
|
||||
<DotLottieReact
|
||||
src="/assets/lottie/cat-fishing.lottie"
|
||||
loop
|
||||
autoplay
|
||||
style={{ width: '240px', height: '240px' }}
|
||||
/>
|
||||
<Typography gutterBottom variant="body1" component="div">
|
||||
Tidak ada berita yang ditemukan
|
||||
</Typography>
|
||||
</Box>
|
||||
)
|
||||
}
|
||||
</Box >
|
||||
);
|
||||
}
|
||||
114
resources/js/pages/home/news/components/MostViewed.jsx
Normal file
114
resources/js/pages/home/news/components/MostViewed.jsx
Normal file
@ -0,0 +1,114 @@
|
||||
import * as React from 'react';
|
||||
import Box from '@mui/material/Box';
|
||||
import Grid from '@mui/material/Grid';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { styled } from '@mui/material/styles';
|
||||
import NavigateNextRoundedIcon from '@mui/icons-material/NavigateNextRounded';
|
||||
import { Chip } from '@mui/material';
|
||||
import Author from './Author';
|
||||
|
||||
const StyledTypography = styled(Typography)({
|
||||
display: '-webkit-box',
|
||||
WebkitBoxOrient: 'vertical',
|
||||
WebkitLineClamp: 2,
|
||||
overflow: 'hidden',
|
||||
textOverflow: 'ellipsis',
|
||||
});
|
||||
|
||||
const TitleTypography = styled(Typography)(({ theme }) => ({
|
||||
position: 'relative',
|
||||
textDecoration: 'none',
|
||||
'&:hover': { cursor: 'pointer' },
|
||||
'& .arrow': {
|
||||
visibility: 'hidden',
|
||||
position: 'absolute',
|
||||
right: 0,
|
||||
top: '50%',
|
||||
transform: 'translateY(-50%)',
|
||||
},
|
||||
'&:hover .arrow': {
|
||||
visibility: 'visible',
|
||||
opacity: 0.7,
|
||||
},
|
||||
'&:focus-visible': {
|
||||
outline: '3px solid',
|
||||
outlineColor: 'hsla(210, 98%, 48%, 0.5)',
|
||||
outlineOffset: '3px',
|
||||
borderRadius: '8px',
|
||||
},
|
||||
'&::before': {
|
||||
content: '""',
|
||||
position: 'absolute',
|
||||
width: 0,
|
||||
height: '1px',
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
backgroundColor: (theme.vars || theme).palette.text.primary,
|
||||
opacity: 0.3,
|
||||
transition: 'width 0.3s ease, opacity 0.3s ease',
|
||||
},
|
||||
'&:hover::before': {
|
||||
width: '100%',
|
||||
},
|
||||
}));
|
||||
|
||||
export default function MostViewed({ mostViewedNews }) {
|
||||
const [focusedCardIndex, setFocusedCardIndex] = React.useState(null);
|
||||
|
||||
const handleFocus = (index) => {
|
||||
setFocusedCardIndex(index);
|
||||
};
|
||||
|
||||
const handleBlur = () => {
|
||||
setFocusedCardIndex(null);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<Typography variant="h2" gutterBottom>
|
||||
Paling Banyak Dilihat
|
||||
</Typography>
|
||||
<Grid container spacing={8} columns={12} sx={{ my: 4 }}>
|
||||
{mostViewedNews.map((news, index) => (
|
||||
<Grid key={index} size={{ xs: 12, sm: 6 }}>
|
||||
<Box
|
||||
sx={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
justifyContent: 'space-between',
|
||||
gap: 1,
|
||||
height: '100%',
|
||||
}}
|
||||
>
|
||||
<Typography gutterBottom variant="caption" component="div">
|
||||
{news.categories.map((category, index) =>
|
||||
<Chip label={category.name} color="success" variant="outlined" sx={{ mr: 1 }} key={index} />
|
||||
)}
|
||||
</Typography>
|
||||
<TitleTypography
|
||||
gutterBottom
|
||||
variant="h6"
|
||||
onFocus={() => handleFocus(index)}
|
||||
onBlur={handleBlur}
|
||||
tabIndex={0}
|
||||
className={focusedCardIndex === index ? 'Mui-focused' : ''}
|
||||
>
|
||||
{news.title}
|
||||
<NavigateNextRoundedIcon
|
||||
className="arrow"
|
||||
sx={{ fontSize: '1rem' }}
|
||||
/>
|
||||
</TitleTypography>
|
||||
|
||||
<StyledTypography variant="body2" color="text.secondary" gutterBottom>
|
||||
{news.description}
|
||||
</StyledTypography>
|
||||
|
||||
<Author author={news.author?.name} publishedAt={news.formatted_published_at} />
|
||||
</Box>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
31
resources/js/pages/home/news/components/Search.jsx
Normal file
31
resources/js/pages/home/news/components/Search.jsx
Normal file
@ -0,0 +1,31 @@
|
||||
import { FormControl, InputAdornment, OutlinedInput } from "@mui/material";
|
||||
import SearchRoundedIcon from '@mui/icons-material/SearchRounded';
|
||||
|
||||
export function Search({ value, onChange, onSearch }) {
|
||||
return (
|
||||
<FormControl sx={{ width: { xs: '100%', md: '100ch' } }} variant="outlined">
|
||||
<OutlinedInput
|
||||
autoComplete='off'
|
||||
size="small"
|
||||
id="search"
|
||||
placeholder="Cari berita..."
|
||||
sx={{ flexGrow: 1 }}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter') {
|
||||
onSearch();
|
||||
}
|
||||
}}
|
||||
startAdornment={
|
||||
<InputAdornment position="start" sx={{ color: 'text.primary' }}>
|
||||
<SearchRoundedIcon fontSize="small" />
|
||||
</InputAdornment>
|
||||
}
|
||||
inputProps={{
|
||||
'aria-label': 'search',
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
);
|
||||
}
|
||||
32
resources/js/pages/home/news/pages/Index.jsx
Normal file
32
resources/js/pages/home/news/pages/Index.jsx
Normal file
@ -0,0 +1,32 @@
|
||||
import CssBaseline from '@mui/material/CssBaseline';
|
||||
import Container from '@mui/material/Container';
|
||||
import AppTheme from '../../../theme/AppTheme';
|
||||
import Navbar from '../../components/Navbar';
|
||||
import MainContent from '../components/MainContent';
|
||||
import MostViewed from '../components/MostViewed';
|
||||
import Footer from '../../components/Footer';
|
||||
|
||||
export default function NewsIndexPage(props) {
|
||||
return (
|
||||
<AppTheme {...props}>
|
||||
<CssBaseline enableColorScheme />
|
||||
|
||||
<Navbar />
|
||||
<Container
|
||||
maxWidth="lg"
|
||||
component="main"
|
||||
sx={{ display: 'flex', flexDirection: 'column', my: 16, gap: 4 }}
|
||||
>
|
||||
<MainContent
|
||||
pageTitle={props.pageTitle}
|
||||
pageDescription={props.pageDescription}
|
||||
categories={props.categories}
|
||||
news={props.news}
|
||||
filters={props.filters}
|
||||
/>
|
||||
<MostViewed mostViewedNews={props.mostViewedNews} />
|
||||
</Container>
|
||||
<Footer />
|
||||
</AppTheme>
|
||||
);
|
||||
}
|
||||
@ -1,6 +1,8 @@
|
||||
<?php
|
||||
|
||||
use App\Http\Controllers\Home\HomepageController;
|
||||
use App\Http\Controllers\Home\NewsController;
|
||||
use Illuminate\Support\Facades\Route;
|
||||
|
||||
Route::get('/', [HomepageController::class, 'index'])->name('homepage');
|
||||
Route::get('/news', [NewsController::class, 'index'])->name('news');
|
||||
|
||||
Loading…
Reference in New Issue
Block a user