diff --git a/README.md b/README.md index 20286ec..48a13b9 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,5 @@ # Hackernews newsletter -## To do - -- Polish the UI - ## Vercel basics Install vercel cli diff --git a/app/api/news/route.ts b/app/api/news/route.ts new file mode 100644 index 0000000..12cfff7 --- /dev/null +++ b/app/api/news/route.ts @@ -0,0 +1,17 @@ +import prisma from '../../../prisma/prisma'; +import { ApiResponse } from '../../../utils/apiResponse'; + +export async function GET() { + const news = await prisma.news.findMany({ + orderBy: { + createdAt: 'desc' + }, + take: 50 + }); + + if (news && news.length === 50) { + return ApiResponse(200, JSON.stringify(news)); + } + + return ApiResponse(500, 'Internal server error'); +} diff --git a/app/globals.css b/app/globals.css index 5cb4d5d..c0a1ebd 100644 --- a/app/globals.css +++ b/app/globals.css @@ -66,16 +66,6 @@ } } -@layer base { - * { - @apply border-border; - } - body { - @apply bg-background text-foreground; - @apply bg-custom-background bg-size-cover; - } -} - .styledH2 { @apply text-xl font-bold; } diff --git a/app/layout.tsx b/app/layout.tsx index 9c8b015..db99d0b 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,6 +1,7 @@ import { Analytics } from '@vercel/analytics/react'; import type { Metadata } from 'next'; import { Inter as FontSans } from 'next/font/google'; +import { Background } from '../components/custom/background/background'; import { cn } from '../utils/utils'; import './globals.css'; @@ -29,7 +30,7 @@ export default function RootLayout({ fontSans.variable )} > - {children} + {children} diff --git a/app/privacy/page.tsx b/app/privacy/page.tsx index 606cfb7..970fb08 100644 --- a/app/privacy/page.tsx +++ b/app/privacy/page.tsx @@ -434,7 +434,7 @@ export default function Privacy() { title='Privacy Policy' description='Last updated: December 03, 2023' content={body} - style='w-2/3' + style='w-2/3 overflow-auto h-[90vh]' /> ); } diff --git a/components/custom/background/background.tsx b/components/custom/background/background.tsx new file mode 100644 index 0000000..75b2ee8 --- /dev/null +++ b/components/custom/background/background.tsx @@ -0,0 +1,9 @@ +import { Tiles } from './components/tiles'; + +type BackgroundProps = { + children: React.ReactNode; +}; + +export function Background({ children }: BackgroundProps) { + return {children}; +} diff --git a/components/custom/background/components/card.tsx b/components/custom/background/components/card.tsx new file mode 100644 index 0000000..42e9de6 --- /dev/null +++ b/components/custom/background/components/card.tsx @@ -0,0 +1,46 @@ +import { useEffect, useState } from 'react'; +import { z } from 'zod'; +import { NewsSchema } from '../../../../utils/types'; +import { Story } from './story'; + +type CardProps = { + newsA?: z.infer; + newsB?: z.infer; +}; + +export function Card({ newsA, newsB }: CardProps) { + const [switched, setSwitched] = useState(false); + const [active, setActive] = useState(Math.random() < 0.5); + const [delayed, setDelayed] = useState(true); + + useEffect(() => { + const randomDelay = Math.floor(Math.random() * 5000); + + const interval = setInterval( + () => { + setSwitched(true); + + window.setTimeout(function () { + setSwitched(false); + setActive(!active); + setDelayed(false); + }, 500 / 2); + }, + delayed ? randomDelay : randomDelay + 3000 + ); + + return () => clearInterval(interval); + }, [active, delayed]); + + if (!newsA || !newsB) return
; + + return ( +
+
+
+ {active ? Story(newsA, true) : Story(newsB, false)} +
+
+
+ ); +} diff --git a/components/custom/background/components/story.tsx b/components/custom/background/components/story.tsx new file mode 100644 index 0000000..054f40e --- /dev/null +++ b/components/custom/background/components/story.tsx @@ -0,0 +1,59 @@ +import { useState } from 'react'; +import { z } from 'zod'; +import { NewsSchema } from '../../../../utils/types'; + +export function Story(story: z.infer, side: boolean) { + const backgroundColors = [ + 'bg-red-300', + 'bg-blue-300', + 'bg-green-300', + 'bg-yellow-300', + 'bg-indigo-300', + 'bg-purple-300', + 'bg-pink-300', + 'bg-gray-300', + 'bg-teal-300', + 'bg-orange-300' + ]; + + const fadingColors = [ + 'to-red-300', + 'to-blue-300', + 'to-green-300', + 'to-yellow-300', + 'to-indigo-300', + 'to-purple-300', + 'to-pink-300', + 'to-gray-300', + 'to-teal-300', + 'to-orange-300' + ]; + + const firstRandom = Math.floor(Math.random() * backgroundColors?.length); + const secondRandom = Math.floor(Math.random() * backgroundColors?.length); + + const [firstColor, setFirstColor] = useState(firstRandom); + const [secondColor, setSecondColor] = useState(secondRandom); + const [switched, setSwitched] = useState(true); + + if (switched !== side) { + setFirstColor(firstRandom); + setSecondColor(secondRandom); + + setSwitched(side); + } + + const colorIndex = side ? firstColor : secondColor; + + return ( +
+

{story.title}

+

{story.by}

+
+
+ ); +} diff --git a/components/custom/background/components/tiles.tsx b/components/custom/background/components/tiles.tsx new file mode 100644 index 0000000..b31a4a0 --- /dev/null +++ b/components/custom/background/components/tiles.tsx @@ -0,0 +1,99 @@ +'use client'; + +import { usePathname } from 'next/navigation'; +import { useEffect, useState } from 'react'; +import { z } from 'zod'; +import { NewsSchema } from '../../../../utils/types'; +import { Card } from './card'; + +type TilesProps = { + children: React.ReactNode; +}; + +export const Tiles = ({ children }: TilesProps) => { + const pathname = usePathname(); + const [windowSize, setWindowSize] = useState<{ + width: number; + height: number; + }>({ + width: 0, + height: 0 + }); + const [news, setNews] = useState[]>(); + + useEffect(() => { + async function getNews() { + const news = await fetch('/api/news').then(res => res.json()); + + if (news) { + setNews(news); + } + } + + if (!news) { + getNews(); + setWindowSize({ + width: window.innerWidth, + height: window.innerHeight + }); + } + + const handleResize = () => { + setWindowSize({ + width: window.innerWidth, + height: window.innerHeight + }); + }; + + window.addEventListener('resize', handleResize); + + return () => { + window.removeEventListener('resize', handleResize); + }; + }, [setWindowSize, news]); + + if (pathname === '/maintenance') return
{children}
; + + function renderTile(key: number) { + if (!news) return
; + + const randomA = Math.floor(Math.random() * news?.length); + const randomB = Math.floor( + Math.random() * news?.filter((_, index) => index !== randomA)?.length + ); + + return ( +
+ +
+ ); + } + + function renderRow(columns: number, key: number) { + return ( +
+ {Array.from({ length: columns }).map((_, index) => renderTile(index))} +
+ ); + } + + function renderGrid() { + const columns = Math.ceil(windowSize.width / (40 * 4)); + const rows = Math.ceil(windowSize.height / (40 * 4)); + + return ( +
+
+ {Array.from({ length: rows }).map((_, index) => + renderRow(columns, index) + )} +
+
+ {children} +
+
+ ); + } + + return
{renderGrid()}
; +}; diff --git a/components/ui/button.tsx b/components/ui/button.tsx index 2e773d2..fa211eb 100644 --- a/components/ui/button.tsx +++ b/components/ui/button.tsx @@ -8,7 +8,8 @@ const buttonVariants = cva( { variants: { variant: { - default: 'bg-primary text-primary-foreground hover:bg-primary/90', + default: + 'bg-primary text-primary-foreground hover:bg-primary/90 bg-blue-500 hover:bg-blue-700', destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90', outline: diff --git a/public/background.jpg b/public/background.jpg deleted file mode 100644 index 6be3bf3..0000000 Binary files a/public/background.jpg and /dev/null differ diff --git a/public/maintenance.html b/public/maintenance.html index f196dd8..2e8ca31 100644 --- a/public/maintenance.html +++ b/public/maintenance.html @@ -1,16 +1,45 @@ - + + - Maintenance + Maintenance :)