From e8893c1aae15f8851d25a4c49f57ba9630ebe04d Mon Sep 17 00:00:00 2001 From: echo Date: Thu, 29 Jan 2026 01:26:48 +0100 Subject: [PATCH] ticket implemented --- backend/src/modules/articles.dto.ts | 1 - frontend/src/lib/api.ts | 10 +- frontend/src/routes.tsx | 186 +++++++++------ frontend/src/routes.tsx.backup | 351 ++++++++++++++++++++++++++++ frontend/src/styles.css | 15 ++ 5 files changed, 492 insertions(+), 71 deletions(-) create mode 100644 frontend/src/routes.tsx.backup diff --git a/backend/src/modules/articles.dto.ts b/backend/src/modules/articles.dto.ts index 560b9af..ef4a002 100644 --- a/backend/src/modules/articles.dto.ts +++ b/backend/src/modules/articles.dto.ts @@ -118,6 +118,5 @@ export class FindArticlesDto { page?: number; @IsOptional() - @IsNumber() limit?: number; } diff --git a/frontend/src/lib/api.ts b/frontend/src/lib/api.ts index 8173450..f013034 100644 --- a/frontend/src/lib/api.ts +++ b/frontend/src/lib/api.ts @@ -50,9 +50,15 @@ export interface FindArticlesParams { export async function fetchArticles(params: FindArticlesParams = {}): Promise { console.log('fetchArticles called with params:', params, 'API_BASE_URL:', API_BASE_URL); const searchParams = new URLSearchParams(); + + // Convert parameters to proper types for URLSearchParams Object.entries(params).forEach(([key, value]) => { - if (value !== undefined) { - searchParams.append(key, String(value)); + if (value !== undefined && value !== null) { + if (typeof value === 'number') { + searchParams.append(key, value.toString()); + } else { + searchParams.append(key, String(value)); + } } }); diff --git a/frontend/src/routes.tsx b/frontend/src/routes.tsx index a0a6418..a60735a 100644 --- a/frontend/src/routes.tsx +++ b/frontend/src/routes.tsx @@ -3,6 +3,54 @@ import { useQuery } from '@tanstack/react-query' import * as api from './lib/api' import './styles.css' +function ArticleTicker() { + const { data } = useQuery({ + queryKey: ['ticker-articles'], + queryFn: () => api.fetchArticles({ status: 'published', limit: 10 }), + }) + + const articles = data?.data.slice(0, 10) || [] + + + + if (articles.length === 0) return null + + return ( +
+
+
+ + Latest: + +
+
+ {articles.map((article, index) => ( + + {article.title || 'No title'} + + ))} + {/* Duplicate for seamless scrolling */} + {articles.map((article, index) => ( + + {article.title || 'No title'} + + ))} +
+
+
+
+
+ ) +} + const rootRoute = createRootRoute({ head: () => ({ meta: [ @@ -47,83 +95,86 @@ const indexRoute = createRoute({ getParentRoute: () => rootRoute, path: '/', component: () => ( -
-
-
- - - - - - - -
-

- Placebo.mk -

-

- Unapologetically sarcastic news and commentary on local and global affairs in Macedonia. Because sometimes the truth hurts more than fiction. -

-
- -
-
-
- - - - - +
+ +
+
+
+ + + + + +
-

Latest Articles

-

- Freshly brewed sarcasm on current events, politics, and everything in between. +

+ Placebo.mk +

+

+ Unapologetically sarcastic news and commentary on local and global affairs in Macedonia. Because sometimes the truth hurts more than fiction.

-
-
- - - - - +
+
+
+ + + + + + +
+

Latest Articles

+

+ Freshly brewed sarcasm on current events, politics, and everything in between. +

+
+ +
+
+ + + + + +
+

No Filter

+

+ We don't do nuance. We don't do diplomatic language. Just honest (and slightly mean) commentary. +

+
+ +
+
+ + + + + + +
+

Community

+

+ Join thousands of readers who appreciate the finer art of Macedonian sarcasm. +

-

No Filter

-

- We don't do nuance. We don't do diplomatic language. Just honest (and slightly mean) commentary. -

-
-
- - - - - +
+ + Browse Articles + + + -
-

Community

-

- Join thousands of readers who appreciate the finer art of Macedonian sarcasm. -

+
- -
- - Browse Articles - - - - - -
), }) @@ -308,5 +359,4 @@ declare module '@tanstack/react-router' { interface Register { router: typeof router } -} - +} \ No newline at end of file diff --git a/frontend/src/routes.tsx.backup b/frontend/src/routes.tsx.backup new file mode 100644 index 0000000..73d1b04 --- /dev/null +++ b/frontend/src/routes.tsx.backup @@ -0,0 +1,351 @@ +import { createRootRoute, createRoute, createRouter, Outlet, Link } from '@tanstack/react-router' +import { useQuery } from '@tanstack/react-query' +import * as api from './lib/api' +import './styles.css' + +function ArticleTicker() { + const { data } = useQuery({ + queryKey: ['ticker-articles'], + queryFn: () => api.fetchArticles({ status: 'published', limit: 10 }), + }) + + const articles = data?.data.slice(0, 10) || [] + + if (articles.length === 0) return null + + return ( +
+
+
+ + Latest: + +
+
+ {articles.map((article) => ( +
+ + {article.title} + +
+ ))} +
+
+
+
+
+ ) +} + +const rootRoute = createRootRoute({ + head: () => ({ + meta: [ + { + title: 'Placebo.mk - Sarcastic News from Macedonia', + description: 'Latest news and articles from Macedonia with a sarcastic twist', + }, + ], + }), + component: () => ( +
+
+
+

+ Placebo.mk +

+ +
+
+ +
+ +
+ +
+
+ © 2025 Placebo.mk. Sarcastic news from Macedonia. +
+
+
+ ), +}) + +const indexRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/', + component: () => ( +
+ +
+
+
+ + + + + + + +
+

+ Placebo.mk +

+

+ Unapologetically sarcastic news and commentary on local and global affairs in Macedonia. Because sometimes the truth hurts more than fiction. +

+
+ +
+
+
+ + + + + + +
+

Latest Articles

+

+ Freshly brewed sarcasm on current events, politics, and everything in between. +

+
+ +
+
+ + + + + +
+

No Filter

+

+ We don't do nuance. We don't do diplomatic language. Just honest (and slightly mean) commentary. +

+
+ +
+
+ + + + + + +
+

Community

+

+ Join thousands of readers who appreciate the finer art of Macedonian sarcasm. +

+
+
+ +
+ + Browse Articles + + + + + +
+
+ ), +}) + +const articlesRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/articles', + component: () => { + const { data, isLoading, error } = useQuery({ + queryKey: ['articles'], + queryFn: () => api.fetchArticles({ status: 'published' }), + }) + + if (isLoading) { + return ( +
+
Loading articles...
+
+ ) + } + + if (error) { + return ( +
+
Error loading articles
+
+ ) + } + + return ( +
+
+

Articles

+

Latest news and articles

+
+ +
+ {data?.data.map((article) => ( + +

+ {article.title} +

+ {article.excerpt && ( +

+ {article.excerpt} +

+ )} +
+ + {new Date(article.createdAt).toLocaleDateString('mk-MK', { + day: 'numeric', + month: 'short', + year: 'numeric', + })} + + {article.views} views +
+ + ))} +
+ + {data?.data.length === 0 && ( +
+

+ No articles published yet. Check back soon! +

+
+ )} +
+ ) + }, +}) + +const articleDetailRoute = createRoute({ + getParentRoute: () => rootRoute, + path: '/articles/$id', + component: () => { + const { id } = articleDetailRoute.useParams() + const { data, isLoading, error } = useQuery({ + queryKey: ['article', id], + queryFn: () => api.fetchArticleById(id), + }) + + if (isLoading) { + return ( +
+
Loading article...
+
+ ) + } + + if (error) { + return ( +
+
Error loading article
+
+ ) + } + + if (!data) { + return ( +
+
Article not found
+
+ ) + } + + return ( +
+ + + + + + Back to articles + + +

{data.title}

+ +
+ + {new Date(data.createdAt).toLocaleDateString('mk-MK', { + day: 'numeric', + month: 'long', + year: 'numeric', + })} + + + {data.views} views + {data.author && ( + <> + + By {data.author.name} + + )} +
+ + {data.featuredImage && ( + {data.title} + )} + +
+

{data.content}

+
+ + {data.tags && Array.isArray(data.tags) && data.tags.length > 0 && ( +
+

Tags

+
+ {data.tags.map((tag) => ( + + {tag} + + ))} +
+
+ )} +
+ ) + }, +}) + +const routeTree = rootRoute.addChildren([indexRoute, articlesRoute, articleDetailRoute]) + +export const router = createRouter({ routeTree }) + +declare module '@tanstack/react-router' { + interface Register { + router: typeof router + } +} + diff --git a/frontend/src/styles.css b/frontend/src/styles.css index 6ac1aaf..a52ce7d 100644 --- a/frontend/src/styles.css +++ b/frontend/src/styles.css @@ -67,6 +67,15 @@ box-sizing: border-box; } +@keyframes marquee { + 0% { + transform: translateX(0); + } + 100% { + transform: translateX(-50%); + } +} + @layer base { * { border-color: hsl(var(--border)); @@ -75,4 +84,10 @@ background-color: hsl(var(--background)); color: hsl(var(--foreground)); } + + .animate-marquee { + display: flex; + animation: marquee 30s linear infinite; + width: max-content; + } }