276 lines
12 KiB
TypeScript
276 lines
12 KiB
TypeScript
import { createRootRoute, createRoute, createRouter, Outlet, Link } from '@tanstack/react-router'
|
|
import { ArticleTicker } from './components/ArticleTicker'
|
|
import { ArticlesComponent } from './components/routes/ArticlesComponent'
|
|
import { ArticleDetailComponent } from './components/routes/ArticleDetailComponent'
|
|
import { LiveBlogsComponent } from './components/routes/LiveBlogsComponent'
|
|
import { LiveBlogDetailComponent } from './components/routes/LiveBlogDetailComponent'
|
|
import { LiveBlogAdminComponent } from './components/routes/LiveBlogAdminComponent'
|
|
import { CreateLiveBlogComponent } from './components/routes/CreateLiveBlogComponent'
|
|
import { AdminDashboardComponent } from './components/routes/AdminDashboardComponent'
|
|
import { AuthPage } from './components/routes/AuthPage'
|
|
import { LiveBlogTicker } from './components/features/live-blog/LiveBlogTicker'
|
|
import { PinnedLiveBlogSidebar } from './components/features/live-blog/PinnedLiveBlogSidebar'
|
|
import { ProtectedRoute } from './components/auth/ProtectedRoute'
|
|
import { Header } from './components/layout/Header'
|
|
import './styles.css'
|
|
|
|
const rootRoute = createRootRoute({
|
|
head: () => ({
|
|
meta: [
|
|
{
|
|
title: 'Placebo.mk - Sarcastic News from Macedonia',
|
|
description: 'Latest news and articles from Macedonia with a sarcastic twist',
|
|
},
|
|
],
|
|
}),
|
|
component: () => (
|
|
<div className="min-h-screen bg-background text-foreground flex flex-col">
|
|
<Header />
|
|
|
|
<main className="flex-1 container mx-auto max-w-6xl px-4 py-8">
|
|
<Outlet />
|
|
</main>
|
|
|
|
<footer className="border-t mt-12">
|
|
<div className="container mx-auto max-w-6xl px-4 py-6 text-center text-sm text-muted-foreground">
|
|
© 2025 Placebo.mk. Sarcastic news from Macedonia.
|
|
</div>
|
|
</footer>
|
|
</div>
|
|
),
|
|
})
|
|
|
|
const indexRoute = createRoute({
|
|
getParentRoute: () => rootRoute,
|
|
path: '/',
|
|
component: () => (
|
|
<div className="space-y-6">
|
|
{/* Article Ticker at the top */}
|
|
<ArticleTicker />
|
|
|
|
{/* Live Blog Ticker below article ticker */}
|
|
<LiveBlogTicker className="mt-4" />
|
|
|
|
<div className="py-8 md:py-12">
|
|
<div className="max-w-7xl mx-auto">
|
|
<div className="grid grid-cols-1 lg:grid-cols-4 gap-8">
|
|
{/* Main content - 3 columns */}
|
|
<div className="lg:col-span-3 space-y-8">
|
|
{/* Hero section */}
|
|
<div className="rounded-xl border bg-card p-8 text-center">
|
|
<div className="inline-flex items-center justify-center w-16 h-16 rounded-full bg-primary/10 mb-6">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-primary">
|
|
<path d="M14.5 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V7.5L14.5 2z" />
|
|
<polyline points="14 2 14 8 20 8" />
|
|
<line x1="16" x2="8" y1="13" y2="13" />
|
|
<line x1="16" x2="8" y1="17" y2="17" />
|
|
<line x1="10" x2="8" y1="9" y2="9" />
|
|
</svg>
|
|
</div>
|
|
<h1 className="text-4xl md:text-5xl font-bold tracking-tight mb-4">
|
|
Placebo<span className="text-primary">.mk</span>
|
|
</h1>
|
|
<p className="text-xl text-muted-foreground max-w-2xl mx-auto">
|
|
Unapologetically sarcastic news and commentary on local and global affairs in Macedonia. Because sometimes the truth hurts more than fiction.
|
|
</p>
|
|
</div>
|
|
|
|
{/* Features grid */}
|
|
<div className="grid md:grid-cols-3 gap-6">
|
|
<div className="p-6 rounded-xl border bg-card hover:shadow-lg transition-all duration-300 group">
|
|
<div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center mb-4 group-hover:bg-primary/20 transition-colors">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-primary">
|
|
<path d="M4 22h16a2 2 0 0 0 2-2V4a2 2 0 0 0-2-2H8a2 2 0 0 0-2 2v16a2 2 0 0 1-2 2Zm0 0a2 2 0 0 1-2-2v-9c0-1.1.9-2 2-2h2" />
|
|
<path d="M18 14h-8" />
|
|
<path d="M15 18h-5" />
|
|
<path d="M10 6h8v4h-8V6Z" />
|
|
</svg>
|
|
</div>
|
|
<h3 className="text-lg font-semibold mb-2">Latest Articles</h3>
|
|
<p className="text-muted-foreground text-sm">
|
|
Freshly brewed sarcasm on current events, politics, and everything in between.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="p-6 rounded-xl border bg-card hover:shadow-lg transition-all duration-300 group">
|
|
<div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center mb-4 group-hover:bg-primary/20 transition-colors">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-primary">
|
|
<circle cx="12" cy="12" r="10" />
|
|
<path d="M9.09 9a3 3 0 0 1 5.83 1c0 2-3 3-3 3" />
|
|
<path d="M12 17h.01" />
|
|
</svg>
|
|
</div>
|
|
<h3 className="text-lg font-semibold mb-2">No Filter</h3>
|
|
<p className="text-muted-foreground text-sm">
|
|
We don't do nuance. We don't do diplomatic language. Just honest (and slightly mean) commentary.
|
|
</p>
|
|
</div>
|
|
|
|
<div className="p-6 rounded-xl border bg-card hover:shadow-lg transition-all duration-300 group">
|
|
<div className="w-10 h-10 rounded-lg bg-primary/10 flex items-center justify-center mb-4 group-hover:bg-primary/20 transition-colors">
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="text-primary">
|
|
<path d="M17 21v-2a4 4 0 0 0-4-4H5a4 4 0 0 0-4 4v2" />
|
|
<circle cx="9" cy="7" r="4" />
|
|
<path d="M23 21v-2a4 4 0 0 0-3-3.87" />
|
|
<path d="M16 3.13a4 4 0 0 1 0 7.75" />
|
|
</svg>
|
|
</div>
|
|
<h3 className="text-lg font-semibold mb-2">Live Coverage</h3>
|
|
<p className="text-muted-foreground text-sm">
|
|
Real-time updates on breaking news with our live blogging system. No delays, just facts.
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Call to action */}
|
|
<div className="rounded-xl border bg-card p-8 text-center">
|
|
<h2 className="text-2xl font-bold mb-4">Ready for some unfiltered truth?</h2>
|
|
<p className="text-muted-foreground mb-6 max-w-2xl mx-auto">
|
|
Dive into our articles or follow live coverage of breaking news as it happens.
|
|
</p>
|
|
<div className="flex flex-col sm:flex-row gap-4 justify-center">
|
|
<Link
|
|
to="/articles"
|
|
className="inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-6 py-2"
|
|
>
|
|
Read Articles
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="ml-2">
|
|
<path d="M5 12h14" />
|
|
<path d="m12 5 7 7-7 7" />
|
|
</svg>
|
|
</Link>
|
|
<Link
|
|
to="/live-blogs"
|
|
className="inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 border border-input bg-background hover:bg-accent hover:text-accent-foreground h-10 px-6 py-2"
|
|
>
|
|
View Live Blogs
|
|
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="ml-2">
|
|
<circle cx="12" cy="12" r="10" />
|
|
<polyline points="12 6 12 12 16 14" />
|
|
</svg>
|
|
</Link>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
{/* Sidebar - 1 column */}
|
|
<div className="lg:col-span-1">
|
|
<PinnedLiveBlogSidebar />
|
|
|
|
{/* Additional sidebar content */}
|
|
<div className="mt-6 rounded-xl border bg-card p-6">
|
|
<h3 className="font-semibold mb-4">About Placebo.mk</h3>
|
|
<p className="text-sm text-muted-foreground mb-4">
|
|
We're not here to make friends. We're here to tell the truth with a healthy dose of sarcasm.
|
|
</p>
|
|
<div className="text-xs text-muted-foreground">
|
|
<div className="flex items-center gap-2 mb-2">
|
|
<div className="w-2 h-2 bg-green-500 rounded-full"></div>
|
|
<span>Live coverage updated in real-time</span>
|
|
</div>
|
|
<div className="flex items-center gap-2 mb-2">
|
|
<div className="w-2 h-2 bg-blue-500 rounded-full"></div>
|
|
<span>No ads, no sponsors, no BS</span>
|
|
</div>
|
|
<div className="flex items-center gap-2">
|
|
<div className="w-2 h-2 bg-purple-500 rounded-full"></div>
|
|
<span>100% independent journalism</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
),
|
|
})
|
|
|
|
const articlesRoute = createRoute({
|
|
getParentRoute: () => rootRoute,
|
|
path: '/articles',
|
|
component: ArticlesComponent,
|
|
})
|
|
|
|
const articleDetailRoute = createRoute({
|
|
getParentRoute: () => rootRoute,
|
|
path: '/articles/$id',
|
|
component: () => {
|
|
const { id } = articleDetailRoute.useParams()
|
|
return <ArticleDetailComponent id={id} />
|
|
},
|
|
})
|
|
|
|
const liveBlogsRoute = createRoute({
|
|
getParentRoute: () => rootRoute,
|
|
path: '/live-blogs',
|
|
component: LiveBlogsComponent,
|
|
})
|
|
|
|
const liveBlogDetailRoute = createRoute({
|
|
getParentRoute: () => rootRoute,
|
|
path: '/live-blogs/$slug',
|
|
component: () => {
|
|
const { slug } = liveBlogDetailRoute.useParams()
|
|
return <LiveBlogDetailComponent slug={slug} />
|
|
},
|
|
})
|
|
|
|
const authRoute = createRoute({
|
|
getParentRoute: () => rootRoute,
|
|
path: '/auth',
|
|
component: AuthPage,
|
|
})
|
|
|
|
const liveBlogAdminRoute = createRoute({
|
|
getParentRoute: () => rootRoute,
|
|
path: '/admin/live-blogs/$slug',
|
|
component: () => {
|
|
const { slug } = liveBlogAdminRoute.useParams()
|
|
return (
|
|
<ProtectedRoute requiredRole="admin">
|
|
<LiveBlogAdminComponent slug={slug} />
|
|
</ProtectedRoute>
|
|
)
|
|
},
|
|
})
|
|
|
|
const createLiveBlogRoute = createRoute({
|
|
getParentRoute: () => rootRoute,
|
|
path: '/admin/live-blogs/create',
|
|
component: () => (
|
|
<ProtectedRoute requiredRole="admin">
|
|
<CreateLiveBlogComponent />
|
|
</ProtectedRoute>
|
|
),
|
|
})
|
|
|
|
const adminDashboardRoute = createRoute({
|
|
getParentRoute: () => rootRoute,
|
|
path: '/admin',
|
|
component: () => (
|
|
<ProtectedRoute requiredRole="admin">
|
|
<AdminDashboardComponent />
|
|
</ProtectedRoute>
|
|
),
|
|
})
|
|
|
|
const routeTree = rootRoute.addChildren([
|
|
indexRoute,
|
|
articlesRoute,
|
|
articleDetailRoute,
|
|
liveBlogsRoute,
|
|
liveBlogDetailRoute,
|
|
authRoute,
|
|
liveBlogAdminRoute,
|
|
createLiveBlogRoute,
|
|
adminDashboardRoute,
|
|
])
|
|
|
|
export const router = createRouter({ routeTree })
|
|
|
|
declare module '@tanstack/react-router' {
|
|
interface Register {
|
|
router: typeof router
|
|
}
|
|
} |