Fix build errors

This commit is contained in:
Fergal Moran
2024-09-29 21:23:03 +01:00
parent 919bf6cbf2
commit 84f6622e63
11 changed files with 63 additions and 274 deletions

View File

@@ -1,3 +1,10 @@
{ {
"reject": ["eslint"] "reject": [
} "eslint",
"typescript",
"@types/eslint",
"@typescript-eslint/eslint-plugin",
"@typescript-eslint/parser",
"eslint-config-next"
]
}

BIN
bun.lockb

Binary file not shown.

View File

@@ -13,7 +13,6 @@
"aliases": { "aliases": {
"components": "@/components", "components": "@/components",
"utils": "@/lib/utils", "utils": "@/lib/utils",
"ui": "@/components/ui", "ui": "@/components/ui"
"magicui": "@/components/magicui"
} }
} }

View File

@@ -100,21 +100,25 @@
"zod": "^3.23.8" "zod": "^3.23.8"
}, },
"devDependencies": { "devDependencies": {
"@types/eslint": "^8.56.10",
"@typescript-eslint/eslint-plugin": "^8.1.0",
"@typescript-eslint/parser": "^8.1.0",
"eslint": "^8.57.0",
"eslint-config-next": "^14.2.4",
"eslint-plugin-drizzle": "^0.2.3",
"@faker-js/faker": "^9.0.3", "@faker-js/faker": "^9.0.3",
"@testing-library/jest-dom": "^6.5.0", "@testing-library/jest-dom": "^6.5.0",
"@testing-library/react": "^16.0.1", "@testing-library/react": "^16.0.1",
"@types/bun": "latest", "@types/bun": "latest",
"@types/eslint": "^9.6.1",
"@types/jest": "^29.5.13", "@types/jest": "^29.5.13",
"@types/node": "^22.7.4", "@types/node": "^22.7.4",
"@types/react": "^18.3.10", "@types/react": "^18.3.10",
"@types/react-dom": "^18.3.0", "@types/react-dom": "^18.3.0",
"@typescript-eslint/eslint-plugin": "^8.7.0",
"@typescript-eslint/parser": "^8.7.0",
"drizzle-kit": "^0.24.2", "drizzle-kit": "^0.24.2",
"eslint": "8.57.0",
"eslint-config-next": "^14.2.13",
"eslint-plugin-drizzle": "^0.2.3",
"jest": "^29.7.0", "jest": "^29.7.0",
"postcss": "^8.4.47", "postcss": "^8.4.47",
"prettier": "^3.3.3", "prettier": "^3.3.3",
@@ -122,7 +126,7 @@
"tailwindcss": "^3.4.13", "tailwindcss": "^3.4.13",
"ts-jest": "^29.2.5", "ts-jest": "^29.2.5",
"ts-node": "^10.9.2", "ts-node": "^10.9.2",
"typescript": "^5.6.2" "typescript": "^5.5.3"
}, },
"ct3aMetadata": { "ct3aMetadata": {
"initVersion": "7.37.0" "initVersion": "7.37.0"

View File

@@ -1,101 +0,0 @@
import { POST } from "./route";
import { NextRequest, NextResponse } from "next/server";
import { StatusCodes } from "http-status-codes";
import fs from "fs";
import path from "path";
import { env } from "@/env";
const mockGetServerSession = require("next-auth").getServerSession;
jest.mock("next-auth", () => ({
getServerSession: jest.fn(),
}));
jest.mock("fs");
jest.mock("path");
describe("POST /api/upload/post", () => {
beforeEach(() => {
jest.clearAllMocks();
});
it("should return UNAUTHORIZED if session is not found", async () => {
mockGetServerSession.mockResolvedValue(null);
const request = new NextRequest(
new URL(`${env.NEXT_PUBLIC_SITE_URL}/api/upload/post`),
);
const response = await POST(request);
expect(response).toEqual(
NextResponse.json({
error: "Unauthorized",
status: StatusCodes.UNAUTHORIZED,
}),
);
});
it("should return BAD_REQUEST if id is not provided", async () => {
mockGetServerSession.mockResolvedValue({ user: { name: "test" } });
const request = new NextRequest(
new URL(`${env.NEXT_PUBLIC_SITE_URL}/api/upload/post`),
);
const response = await POST(request);
expect(response).toEqual(
NextResponse.json({
error: "No post id in query",
status: StatusCodes.BAD_REQUEST,
}),
);
});
it("should return BAD_REQUEST if file is not found in form data", async () => {
mockGetServerSession.mockResolvedValue({ user: { name: "test" } });
const url = new URL(`${env.NEXT_PUBLIC_SITE_URL}/api/upload/post?id=123`);
const request = new NextRequest(url);
request.formData = jest.fn().mockResolvedValue(new FormData());
const response = await POST(request);
expect(response).toEqual(
NextResponse.json({
error: "Cannot find file in form data",
status: StatusCodes.BAD_REQUEST,
}),
);
});
it("should save the file and return success response", async () => {
mockGetServerSession.mockResolvedValue({ user: { name: "test" } });
const url = new URL(`${env.NEXT_PUBLIC_SITE_URL}/api/upload/post?id=123`);
const request = new NextRequest(url);
const file = new Blob(["file content"], { type: "image/png" });
const formData = new FormData();
formData.append("image", file, "test.png");
request.formData = jest.fn().mockResolvedValue(formData);
const buffer = Buffer.from(await file.arrayBuffer());
const filePath = "123.png";
jest.spyOn(fs, 'writeFileSync').mockImplementation(() => {});
jest.spyOn(path, 'resolve').mockReturnValue(`/mocked/path/${filePath}`);
const response = await POST(request);
expect(fs.writeFileSync).toHaveBeenCalledWith(
`/mocked/path/${filePath}`,
buffer,
);
expect(response).toEqual(
NextResponse.json({
success: true,
url: env.NEXT_PUBLIC_SITE_URL + `/i/${filePath}`,
}),
);
});
});

View File

@@ -1,79 +0,0 @@
import { ReactNode } from "react";
import { ArrowRightIcon } from "@radix-ui/react-icons";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
const BentoGrid = ({
children,
className,
}: {
children: ReactNode;
className?: string;
}) => {
return (
<div
className={cn(
"grid w-full auto-rows-[22rem] grid-cols-3 gap-4",
className,
)}
>
{children}
</div>
);
};
const BentoCard = ({
name,
className,
background,
Icon,
description,
href,
cta,
}: {
name: string;
className: string;
background: ReactNode;
Icon: any;
description: string;
href: string;
cta: string;
}) => (
<div
key={name}
className={cn(
"group relative col-span-3 flex flex-col justify-between overflow-hidden rounded-xl",
// light styles
"bg-white [box-shadow:0_0_0_1px_rgba(0,0,0,.03),0_2px_4px_rgba(0,0,0,.05),0_12px_24px_rgba(0,0,0,.05)]",
// dark styles
"transform-gpu dark:bg-black dark:[border:1px_solid_rgba(255,255,255,.1)] dark:[box-shadow:0_-20px_80px_-20px_#ffffff1f_inset]",
className,
)}
>
<div>{background}</div>
<div className="pointer-events-none z-10 flex transform-gpu flex-col gap-1 p-6 transition-all duration-300 group-hover:-translate-y-10">
<Icon className="h-12 w-12 origin-left transform-gpu text-neutral-700 transition-all duration-300 ease-in-out group-hover:scale-75" />
<h3 className="text-xl font-semibold text-neutral-700 dark:text-neutral-300">
{name}
</h3>
<p className="max-w-lg text-neutral-400">{description}</p>
</div>
<div
className={cn(
"pointer-events-none absolute bottom-0 flex w-full translate-y-10 transform-gpu flex-row items-center p-4 opacity-0 transition-all duration-300 group-hover:translate-y-0 group-hover:opacity-100",
)}
>
<Button variant="ghost" asChild size="sm" className="pointer-events-auto">
<a href={href}>
{cta}
<ArrowRightIcon className="ml-2 h-4 w-4" />
</a>
</Button>
</div>
<div className="pointer-events-none absolute inset-0 transform-gpu transition-all duration-300 group-hover:bg-black/[.03] group-hover:dark:bg-neutral-800/10" />
</div>
);
export { BentoCard, BentoGrid };

View File

@@ -1,39 +0,0 @@
"use client";
import { motion } from "framer-motion";
import { cn } from "@/lib/utils";
interface BlurIntProps {
word: string;
className?: string;
variant?: {
hidden: { filter: string; opacity: number };
visible: { filter: string; opacity: number };
};
duration?: number;
}
const BlurIn = ({ word, className, variant, duration = 1 }: BlurIntProps) => {
const defaultVariants = {
hidden: { filter: "blur(10px)", opacity: 0 },
visible: { filter: "blur(0px)", opacity: 1 },
};
const combinedVariants = variant || defaultVariants;
return (
<motion.h1
initial="hidden"
animate="visible"
transition={{ duration }}
variants={combinedVariants}
className={cn(
"font-display text-center text-4xl font-bold tracking-[-0.02em] drop-shadow-sm md:text-7xl md:leading-[5rem]",
className,
)}
>
{word}
</motion.h1>
);
};
export default BlurIn;

View File

@@ -27,7 +27,7 @@ const PostActions: React.FC<PostActionsProps> = ({ post }) => {
title="Downvote" title="Downvote"
action={async () => { action={async () => {
await vote.mutateAsync({ slug: post.slug, up: false }); await vote.mutateAsync({ slug: post.slug, up: false });
voteCount.refetch(); await voteCount.refetch();
}} }}
icon={<Icons.down className="h-6 w-6" />} icon={<Icons.down className="h-6 w-6" />}
/> />
@@ -35,7 +35,7 @@ const PostActions: React.FC<PostActionsProps> = ({ post }) => {
title="Favourite" title="Favourite"
action={async () => { action={async () => {
await vote.mutateAsync({ slug: post.slug, up: false }); await vote.mutateAsync({ slug: post.slug, up: false });
voteCount.refetch(); await voteCount.refetch();
}} }}
icon={<Icons.heart className="h-6 w-6" />} icon={<Icons.heart className="h-6 w-6" />}
/> />

View File

@@ -1,8 +1,7 @@
import { api } from "@/trpc/server"; import { api } from "@/trpc/server";
import BlurIn from "./magicui/blur-in";
import Link from "next/link"; import Link from "next/link";
import { Icons } from "./icons"; import { Icons } from "./icons";
import { Post } from "@/lib/models/post"; import { type Post } from "@/lib/models/post";
import { import {
Card, Card,
CardContent, CardContent,
@@ -12,14 +11,15 @@ import {
} from "@/components/ui/card"; } from "@/components/ui/card";
import { Button } from "./ui/button"; import { Button } from "./ui/button";
import VoteCount from "./widgets/vote-count"; import VoteCount from "./widgets/vote-count";
import Loading from "@/components/widgets/loading";
export const TrendingPosts: React.FC = async () => { export const TrendingPosts: React.FC = async () => {
const trendingPosts = await api.post.getTrending(); const trendingPosts = await api.post.getTrending();
return trendingPosts.length !== 0 ? ( return trendingPosts.length !== 0 ? (
<div className="masonry sm:masonry-sm md:masonry-md lg:masonry-lg"> <div className="masonry sm:masonry-sm md:masonry-md lg:masonry-lg">
{trendingPosts.map((post: Post) => ( {trendingPosts.map((post: Post) => (
<div className="py-2"> <div className="py-2" key={post.slug}>
<Link href={`/post/${post.slug}`} key={post.slug}> <Link href={`/post/${post.slug}`}>
<Card className="overflow-hidden"> <Card className="overflow-hidden">
<CardHeader className="p-0"> <CardHeader className="p-0">
<img <img
@@ -52,9 +52,6 @@ export const TrendingPosts: React.FC = async () => {
))} ))}
</div> </div>
) : ( ) : (
<BlurIn <Loading />
word="No images yet"
className="scroll-m-20 text-3xl font-bold tracking-tight"
/>
); );
}; };

View File

@@ -14,6 +14,7 @@ const VoteCount: React.FC<VoteCountProps> = ({ post }) => {
return ( return (
<span> <span>
{voteCount.data ? ( {voteCount.data ? (
// eslint-disable-next-line @typescript-eslint/no-base-to-string, @typescript-eslint/restrict-template-expressions
`${voteCount.data.voteCount} votes` `${voteCount.data.voteCount} votes`
) : ( ) : (
<Icons.spinner className="animate-spin" /> <Icons.spinner className="animate-spin" />

View File

@@ -1,38 +1,38 @@
import { NextRequest, NextResponse, URLPattern } from "next/server"; // import { type NextRequest, NextResponse, URLPattern } from "next/server";
export { default } from "next-auth/middleware"; export { default } from "next-auth/middleware";
export const config = { // export const config = {
matcher: ["/upload(.*)", "/i/:id(.*)"], // matcher: ["/upload(.*)", "/i/:id(.*)"],
}; // };
const PATTERNS: [ // const PATTERNS: [
URLPattern, // URLPattern,
({ pathname }: { pathname: { groups: any } }) => any, // ({ pathname }: { pathname: { groups: any } }) => any,
][] = [ // ][] = [
[ // [
new URLPattern({ pathname: "/i/:id" }), // new URLPattern({ pathname: "/i/:id" }),
({ pathname }: { pathname: { groups: any } }) => pathname.groups, // ({ pathname }: { pathname: { groups: any } }) => pathname.groups,
], // ],
]; // ];
const params = (url: string) => { // const params = (url: string) => {
const input = url.split("?")[0]; // const input = url.split("?")[0];
let result = {}; // let result = {};
for (const [pattern, handler] of PATTERNS) { // for (const [pattern, handler] of PATTERNS) {
if (!pattern) return; // if (!pattern) return;
const patternResult = pattern.exec(input); // const patternResult = pattern.exec(input);
if (patternResult !== null && "pathname" in patternResult) { // if (patternResult !== null && "pathname" in patternResult) {
result = handler(patternResult); // result = handler(patternResult);
break; // break;
} // }
} // }
return result || {}; // return result || {};
}; // };
export async function middleware(request: NextRequest, response: NextResponse) { // export async function middleware(request: NextRequest, response: NextResponse) {
const { id } = params(request.url) as { id?: string }; // const { id } = params(request.url) as { id?: string };
if (!id) { // if (!id) {
return NextResponse.next(); // return NextResponse.next();
} // }
return NextResponse.rewrite(new URL(`/uploads/${id}`, request.url)); // return NextResponse.rewrite(new URL(`/uploads/${id}`, request.url));
} // }