87 | >(({ className, ...props }, ref) => (
88 | [role=checkbox]]:translate-y-[2px]",
92 | className
93 | )}
94 | {...props}
95 | />
96 | ))
97 | TableCell.displayName = "TableCell"
98 |
99 | const TableCaption = React.forwardRef<
100 | HTMLTableCaptionElement,
101 | React.HTMLAttributes
102 | >(({ className, ...props }, ref) => (
103 |
108 | ))
109 | TableCaption.displayName = "TableCaption"
110 |
111 | export {
112 | Table,
113 | TableHeader,
114 | TableBody,
115 | TableFooter,
116 | TableHead,
117 | TableRow,
118 | TableCell,
119 | TableCaption,
120 | }
121 |
--------------------------------------------------------------------------------
/src/lib/prisma.ts:
--------------------------------------------------------------------------------
1 | import {PrismaClient} from '@prisma/client'
2 | export const prisma = new PrismaClient();
3 |
--------------------------------------------------------------------------------
/src/lib/query/fetch-articles.ts:
--------------------------------------------------------------------------------
1 | import { prisma } from "@/lib/prisma";
2 |
3 | export const fetchArticleByQuery = async (searchText: string, skip: number, take: number) => {
4 | const [articles, total] = await prisma.$transaction([
5 | prisma.articles.findMany({
6 | where: {
7 | OR: [
8 | { title: { contains: searchText, mode: 'insensitive' } },
9 | { category: { contains: searchText, mode: 'insensitive' } },
10 | ],
11 | },
12 | include: {
13 | author: {
14 | select: { name: true, imageUrl: true, email: true },
15 | },
16 | },
17 | skip: skip,
18 | take: take,
19 | }),
20 | prisma.articles.count({
21 | where: {
22 | OR: [
23 | { title: { contains: searchText, mode: 'insensitive' } },
24 | { category: { contains: searchText, mode: 'insensitive' } },
25 | ],
26 | },
27 | }),
28 | ]);
29 |
30 | return { articles, total };
31 | };
--------------------------------------------------------------------------------
/src/lib/utils.ts:
--------------------------------------------------------------------------------
1 | import { clsx, type ClassValue } from "clsx"
2 | import { twMerge } from "tailwind-merge"
3 |
4 | export function cn(...inputs: ClassValue[]) {
5 | return twMerge(clsx(inputs))
6 | }
7 |
--------------------------------------------------------------------------------
/src/middleware.ts:
--------------------------------------------------------------------------------
1 | import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'
2 | import { NextRequest } from 'next/server';
3 |
4 | const isProtectedRoute = createRouteMatcher(["/dashboard(.*)","/articles(.*)"]);
5 |
6 | export default clerkMiddleware(async (auth, req: NextRequest) => {
7 | if (isProtectedRoute(req)) {
8 | await auth.protect();
9 | }
10 | });
11 |
12 | export const config = {
13 | matcher: [
14 | // Skip Next.js internals and all static files, unless found in search params
15 | '/((?!_next|[^?]*\\.(?:html?|css|js(?!on)|jpe?g|webp|png|gif|svg|ttf|woff2?|ico|csv|docx?|xlsx?|zip|webmanifest)).*)',
16 | // Always run for API routes
17 | '/(api|trpc)(.*)',
18 | ],
19 | }
--------------------------------------------------------------------------------
/tailwind.config.ts:
--------------------------------------------------------------------------------
1 | import type { Config } from "tailwindcss";
2 |
3 | export default {
4 | darkMode: ["class"],
5 | content: [
6 | "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
7 | "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
8 | "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
9 | ],
10 | theme: {
11 | extend: {
12 | colors: {
13 | background: 'var(--background)',
14 | foreground: 'var(--foreground)'
15 | },
16 | borderRadius: {
17 | lg: 'var(--radius)',
18 | md: 'calc(var(--radius) - 2px)',
19 | sm: 'calc(var(--radius) - 4px)'
20 | }
21 | }
22 | },
23 | plugins: [require("tailwindcss-animate")],
24 | } satisfies Config;
25 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "ES2017",
4 | "lib": ["dom", "dom.iterable", "esnext"],
5 | "allowJs": true,
6 | "skipLibCheck": true,
7 | "strict": true,
8 | "noEmit": true,
9 | "esModuleInterop": true,
10 | "module": "esnext",
11 | "moduleResolution": "bundler",
12 | "resolveJsonModule": true,
13 | "isolatedModules": true,
14 | "jsx": "preserve",
15 | "incremental": true,
16 | "plugins": [
17 | {
18 | "name": "next"
19 | }
20 | ],
21 | "paths": {
22 | "@/*": ["./src/*"]
23 | }
24 | },
25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
26 | "exclude": ["node_modules"]
27 | }
28 |
--------------------------------------------------------------------------------
|