Cardify the main feed

This commit is contained in:
Fergal Moran
2024-09-23 19:42:42 +01:00
parent 3421639cf1
commit 7311d3524f
9 changed files with 226 additions and 85 deletions

BIN
bun.lockb

Binary file not shown.

View File

@@ -68,14 +68,14 @@
"date-fns": "^4.1.0",
"drizzle-orm": "^0.33.0",
"embla-carousel-react": "^8.3.0",
"framer-motion": "^11.5.5",
"framer-motion": "^11.5.6",
"geist": "^1.3.1",
"http-status-codes": "^2.3.0",
"input-otp": "^1.2.4",
"lodash": "^4.17.21",
"loglevel": "^1.9.2",
"loglevel-plugin-prefix": "^0.8.4",
"lucide-react": "^0.441.0",
"lucide-react": "^0.445.0",
"mime-types": "^2.1.35",
"next": "^14.2.13",
"next-auth": "^4.24.7",
@@ -84,7 +84,7 @@
"postgres": "^3.4.4",
"react": "^18.3.1",
"react-copy-to-clipboard": "^5.1.0",
"react-day-picker": "9.1.1",
"react-day-picker": "9.1.2",
"react-dom": "^18.3.1",
"react-hook-form": "^7.53.0",
"react-icons": "^5.3.0",
@@ -112,7 +112,7 @@
"@typescript-eslint/eslint-plugin": "^8.6.0",
"@typescript-eslint/parser": "^8.6.0",
"drizzle-kit": "^0.24.2",
"eslint": "^9.10.0",
"eslint": "^9.11.0",
"eslint-config-next": "^14.2.13",
"eslint-plugin-drizzle": "^0.2.3",
"jest": "^29.7.0",

View File

@@ -12,7 +12,7 @@ const PostActions: React.FC<PostActionsProps> = ({ post }) => {
const vote = api.post.vote.useMutation();
const voteCount = api.post.getVoteCount.useQuery({ slug: post.slug });
return (
<div className="flex flex-col items-center space-y-4 border p-4 shadow-lg">
<div className="flex flex-col items-center space-y-4 rounded-md border p-4">
<ActionButton
title="Upvote"
action={async () => {

View File

@@ -18,16 +18,19 @@ const PostPage: React.FC<PostPageProps> = ({ post }) => {
</div>
</div>
<div id="centre" className="flex-grow">
<div>
<h2 className="mb-2 text-xl font-bold">{post.title}</h2>
<div className="rounded-t-md border-l border-r border-t bg-card text-card-foreground shadow">
<div className="flex flex-col">
<div className="flex justify-center p-4">
<img src={post.imageUrl} className="object-cover" />
<img
src={post.imageUrl}
className="h-auto max-w-full rounded-lg object-cover"
/>
</div>
</div>
</div>
<div className="mt-8">
<h4 className="text-xl font-bold">{post.title}</h4>
<p className="mt-2 text-gray-600">{post.description}</p>
<div className="rounded-b-md border-b border-l border-r bg-muted">
<p className="p-4 text-sm opacity-75">{post.description}</p>
</div>
</div>
<div id="right" className="w-1/6 flex-none">

View File

@@ -4,6 +4,14 @@ import Link from "next/link";
import { Icons } from "./icons";
import ActionButton from "./widgets/action-button";
import { Post } from "@/lib/models/post";
import {
Card,
CardContent,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import { Button } from "./ui/button";
const upvote = async () => {
"use server";
@@ -19,45 +27,36 @@ export const TrendingPosts: React.FC = async () => {
return trendingPosts.length !== 0 ? (
<div className="masonry sm:masonry-sm md:masonry-md">
{trendingPosts.map((post: Post) => (
<Link href={`/post/${post.slug}`} key={post.slug}>
<div className="break-inside rounded-lg">
<div className="relative m-6 flex-shrink-0 overflow-hidden rounded-lg bg-muted shadow-lg hover:bg-accent">
<div className="relative flex items-center justify-center px-2 pt-4">
<div className="p-4">
<Link href={`/post/${post.slug}`} key={post.slug}>
<Card className="overflow-hidden">
<CardHeader className="p-0">
<img
className="h-auto max-w-full rounded-lg"
src={post.imageUrl}
alt={post.description ?? "An image"}
alt={post.title}
className="h-auto w-full object-cover"
/>
</div>
<div className="relative mt-6 space-y-2 px-6">
<span className="text-md block font-semibold">
{post.title}
</span>
<div className="flex justify-between pb-2">
<div className="pr-2">
<div className="line-clamp-1 align-middle text-sm opacity-75">
{post.description}
</div>
</div>
<span className="block items-center rounded-full text-xs font-bold leading-none">
<div className="flex space-x-1">
<ActionButton
title="Upvote"
action={upvote}
icon={<Icons.up />}
/>
<ActionButton
title="Downvote"
action={downvote}
icon={<Icons.down />}
/>
</div>
</span>
</CardHeader>
<CardContent className="p-4">
<CardTitle>{post.title}</CardTitle>
<p className="mt-2 text-sm text-muted-foreground">
{post.description}
</p>
</CardContent>
<CardFooter className="flex items-center justify-between p-4">
<div className="flex items-center space-x-2">
<Button variant="outline" size="icon">
<Icons.up className="h-4 w-4" />
</Button>
<Button variant="outline" size="icon">
<Icons.down className="h-4 w-4" />
</Button>
</div>
</div>
</div>
</div>
</Link>
<span className="font-semibold">{post.likes} votes</span>
</CardFooter>
</Card>
</Link>
</div>
))}
</div>
) : (

View File

@@ -0,0 +1,76 @@
import * as React from "react"
import { cn } from "@/lib/utils"
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-xl border bg-card text-card-foreground shadow",
className
)}
{...props}
/>
))
Card.displayName = "Card"
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
))
CardHeader.displayName = "CardHeader"
const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn("font-semibold leading-none tracking-tight", className)}
{...props}
/>
))
CardTitle.displayName = "CardTitle"
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
CardDescription.displayName = "CardDescription"
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
))
CardContent.displayName = "CardContent"
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
))
CardFooter.displayName = "CardFooter"
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent }

View File

@@ -10,10 +10,7 @@ type ActionButtonProps = {
const ActionButton: React.FC<ActionButtonProps> = ({ title, action, icon }) => {
return (
<div
onClick={action}
className="cursor-pointer hover:text-primary-foreground"
>
<div onClick={action} className="cursor-pointer hover:text-primary">
{icon}
</div>
);

View File

@@ -2,6 +2,30 @@
@tailwind components;
@tailwind utilities;
:root {
--background: #ffffff;
--foreground: #171717;
}
@media (prefers-color-scheme: dark) {
:root {
--background: #0a0a0a;
--foreground: #ededed;
}
}
body {
color: var(--foreground);
background: var(--background);
font-family: Arial, Helvetica, sans-serif;
}
@layer utilities {
.text-balance {
text-wrap: balance;
}
}
@layer base {
:root {
--background: 0 0% 100%;
@@ -23,14 +47,13 @@
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 240 10% 3.9%;
--radius: 0.5rem;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
--radius: 0.5rem;
}
.dark {
--background: 240 10% 3.9%;
--foreground: 0 0% 98%;
@@ -68,6 +91,63 @@
}
}
@layer base {
:root {
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--primary: 346.8 77.2% 49.8%;
--primary-foreground: 355.7 100% 97.3%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 346.8 77.2% 49.8%;
--radius: 0.5rem;
--chart-1: 12 76% 61%;
--chart-2: 173 58% 39%;
--chart-3: 197 37% 24%;
--chart-4: 43 74% 66%;
--chart-5: 27 87% 67%;
}
.dark {
--background: 20 14.3% 4.1%;
--foreground: 0 0% 95%;
--card: 24 9.8% 10%;
--card-foreground: 0 0% 95%;
--popover: 0 0% 9%;
--popover-foreground: 0 0% 95%;
--primary: 346.8 77.2% 49.8%;
--primary-foreground: 355.7 100% 97.3%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 15%;
--muted-foreground: 240 5% 64.9%;
--accent: 12 6.5% 15.1%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 85.7% 97.3%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 346.8 77.2% 49.8%;
--chart-1: 220 70% 50%;
--chart-2: 160 60% 45%;
--chart-3: 30 80% 55%;
--chart-4: 280 65% 60%;
--chart-5: 340 75% 55%;
}
}
@layer utilities {
@variants responsive {
.masonry {

View File

@@ -10,20 +10,18 @@ const config = {
],
prefix: "",
theme: {
container: {
center: true,
padding: "2rem",
screens: {
"2xl": "1400px",
},
},
extend: {
colors: {
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
background: "hsl(var(--background))",
foreground: "hsl(var(--foreground))",
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
},
primary: {
DEFAULT: "hsl(var(--primary))",
foreground: "hsl(var(--primary-foreground))",
@@ -32,10 +30,6 @@ const config = {
DEFAULT: "hsl(var(--secondary))",
foreground: "hsl(var(--secondary-foreground))",
},
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
muted: {
DEFAULT: "hsl(var(--muted))",
foreground: "hsl(var(--muted-foreground))",
@@ -44,13 +38,19 @@ const config = {
DEFAULT: "hsl(var(--accent))",
foreground: "hsl(var(--accent-foreground))",
},
popover: {
DEFAULT: "hsl(var(--popover))",
foreground: "hsl(var(--popover-foreground))",
destructive: {
DEFAULT: "hsl(var(--destructive))",
foreground: "hsl(var(--destructive-foreground))",
},
card: {
DEFAULT: "hsl(var(--card))",
foreground: "hsl(var(--card-foreground))",
border: "hsl(var(--border))",
input: "hsl(var(--input))",
ring: "hsl(var(--ring))",
chart: {
"1": "hsl(var(--chart-1))",
"2": "hsl(var(--chart-2))",
"3": "hsl(var(--chart-3))",
"4": "hsl(var(--chart-4))",
"5": "hsl(var(--chart-5))",
},
},
borderRadius: {
@@ -58,20 +58,6 @@ const config = {
md: "calc(var(--radius) - 2px)",
sm: "calc(var(--radius) - 4px)",
},
keyframes: {
"accordion-down": {
from: { height: "0" },
to: { height: "var(--radix-accordion-content-height)" },
},
"accordion-up": {
from: { height: "var(--radix-accordion-content-height)" },
to: { height: "0" },
},
},
animation: {
"accordion-down": "accordion-down 0.2s ease-out",
"accordion-up": "accordion-up 0.2s ease-out",
},
},
},
plugins: [require("tailwindcss-animate")],