mirror of
https://github.com/fergalmoran/opengifame.git
synced 2025-12-22 09:38:44 +00:00
Cardify the main feed
This commit is contained in:
@@ -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",
|
||||
|
||||
@@ -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 () => {
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
) : (
|
||||
|
||||
76
src/components/ui/card.tsx
Normal file
76
src/components/ui/card.tsx
Normal 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 }
|
||||
@@ -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>
|
||||
);
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")],
|
||||
|
||||
Reference in New Issue
Block a user