├── middleware.ts ├── .idea ├── .gitignore ├── vcs.xml ├── fileColors.xml ├── jsLinters │ └── eslint.xml ├── inspectionProfiles │ └── Project_Default.xml ├── modules.xml ├── prettier.xml ├── material_theme_project_new.xml └── university-library.iml ├── migrations ├── 0002_special_vapor.sql ├── 0001_lucky_shape.sql ├── 0003_worthless_goblin_queen.sql ├── meta │ └── _journal.json └── 0000_worthless_invaders.sql ├── app ├── favicon.ico ├── api │ ├── auth │ │ ├── [...nextauth] │ │ │ └── route.ts │ │ └── imagekit │ │ │ └── route.ts │ └── workflow │ │ ├── onboarding │ │ └── route.ts │ │ └── borrow-book │ │ └── route.ts ├── fonts │ ├── IBMPlexSans-Bold.ttf │ ├── BebasNeue-Regular.ttf │ ├── IBMPlexSans-Medium.ttf │ ├── IBMPlexSans-Regular.ttf │ └── IBMPlexSans-SemiBold.ttf ├── (auth) │ ├── sign-in │ │ └── page.tsx │ ├── sign-up │ │ └── page.tsx │ └── layout.tsx ├── admin │ ├── books │ │ ├── new │ │ │ └── page.tsx │ │ ├── [id] │ │ │ ├── edit │ │ │ │ └── page.tsx │ │ │ └── page.tsx │ │ └── page.tsx │ ├── layout.tsx │ ├── page.tsx │ ├── account-requests │ │ └── page.tsx │ ├── users │ │ └── page.tsx │ └── borrow-records │ │ └── page.tsx ├── too-fast │ └── page.tsx ├── (root) │ ├── page.tsx │ ├── library │ │ └── page.tsx │ ├── layout.tsx │ ├── books │ │ └── [id] │ │ │ └── page.tsx │ └── my-profile │ │ └── page.tsx └── layout.tsx ├── public ├── favicon.ico ├── images │ ├── no-books.png │ ├── pattern.webp │ └── auth-illustration.png └── icons │ ├── home.svg │ ├── admin │ ├── search.svg │ ├── caret-down.svg │ ├── caret-up.svg │ ├── eye.svg │ ├── plus.svg │ ├── tick.svg │ ├── user.svg │ ├── home.svg │ ├── close.svg │ ├── link.svg │ ├── info.svg │ ├── calendar.svg │ ├── edit.svg │ ├── bookmark.svg │ ├── logo.svg │ ├── trash.svg │ ├── book.svg │ ├── receipt.svg │ └── users.svg │ ├── logout.svg │ ├── search-fill.svg │ ├── clock.svg │ ├── tick.svg │ ├── heart.svg │ ├── user.svg │ ├── calendar.svg │ ├── user-fill.svg │ ├── book.svg │ ├── logo.svg │ ├── star.svg │ ├── warning.svg │ ├── upload.svg │ ├── id.svg │ ├── book-2.svg │ ├── verified.svg │ └── receipt.svg ├── prettierrc.json ├── .eslintrc.json ├── postcss.config.mjs ├── database ├── drizzle.ts ├── redis.ts ├── schema.ts └── seed.ts ├── lib ├── ratelimit.ts ├── utils.ts ├── config.ts ├── workflow.ts ├── validations.ts ├── admin │ └── actions │ │ ├── general.ts │ │ └── user.ts └── actions │ ├── auth.ts │ └── book.ts ├── drizzle.config.ts ├── components ├── admin │ ├── ErrorFallback.tsx │ ├── UserCard.tsx │ ├── Header.tsx │ ├── home │ │ ├── BorrowRequests.tsx │ │ ├── AccountRequests.tsx │ │ ├── RecentBooks.tsx │ │ └── Statistics.tsx │ ├── ColorPicker.tsx │ ├── BookStripe.tsx │ ├── Loading.tsx │ ├── Search.tsx │ ├── dialogs │ │ ├── AccountConfirmation.tsx │ │ └── ConfirmationDialog.tsx │ ├── Menu.tsx │ └── Sidebar.tsx ├── BookVideo.tsx ├── ui │ ├── label.tsx │ ├── textarea.tsx │ ├── toaster.tsx │ ├── input.tsx │ ├── button.tsx │ ├── table.tsx │ └── dialog.tsx ├── Avatar.tsx ├── NotFound.tsx ├── Header.tsx ├── Sort.tsx ├── BookList.tsx ├── BookCover.tsx ├── Search.tsx ├── Pagination.tsx ├── BorrowBook.tsx ├── BookOverview.tsx ├── BookCard.tsx ├── BookCoverSvg.tsx └── forms │ └── AuthForm.tsx ├── components.json ├── .vscode └── settings.json ├── .gitignore ├── next.config.ts ├── eslint.config.mjs ├── tsconfig.json ├── auth.ts ├── tailwind.config.ts ├── types.d.ts └── package.json /middleware.ts: -------------------------------------------------------------------------------- 1 | export { auth as middleware } from "@/auth"; 2 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | -------------------------------------------------------------------------------- /migrations/0002_special_vapor.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "borrow_records" ALTER COLUMN "status" SET NOT NULL; -------------------------------------------------------------------------------- /migrations/0001_lucky_shape.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "borrow_records" ALTER COLUMN "borrow_date" SET NOT NULL; -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavaScript-Mastery-Pro/university-library/HEAD/app/favicon.ico -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavaScript-Mastery-Pro/university-library/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /app/api/auth/[...nextauth]/route.ts: -------------------------------------------------------------------------------- 1 | import { handlers } from "@/auth"; 2 | export const { GET, POST } = handlers; 3 | -------------------------------------------------------------------------------- /prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "max-line-length": [ 3 | true, 4 | { 5 | "limit": 140 6 | } 7 | ] 8 | } -------------------------------------------------------------------------------- /public/images/no-books.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavaScript-Mastery-Pro/university-library/HEAD/public/images/no-books.png -------------------------------------------------------------------------------- /public/images/pattern.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavaScript-Mastery-Pro/university-library/HEAD/public/images/pattern.webp -------------------------------------------------------------------------------- /app/fonts/IBMPlexSans-Bold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavaScript-Mastery-Pro/university-library/HEAD/app/fonts/IBMPlexSans-Bold.ttf -------------------------------------------------------------------------------- /migrations/0003_worthless_goblin_queen.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE "borrow_records" ADD COLUMN "created_at" timestamp with time zone DEFAULT now(); -------------------------------------------------------------------------------- /app/fonts/BebasNeue-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavaScript-Mastery-Pro/university-library/HEAD/app/fonts/BebasNeue-Regular.ttf -------------------------------------------------------------------------------- /app/fonts/IBMPlexSans-Medium.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavaScript-Mastery-Pro/university-library/HEAD/app/fonts/IBMPlexSans-Medium.ttf -------------------------------------------------------------------------------- /app/fonts/IBMPlexSans-Regular.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavaScript-Mastery-Pro/university-library/HEAD/app/fonts/IBMPlexSans-Regular.ttf -------------------------------------------------------------------------------- /app/fonts/IBMPlexSans-SemiBold.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavaScript-Mastery-Pro/university-library/HEAD/app/fonts/IBMPlexSans-SemiBold.ttf -------------------------------------------------------------------------------- /public/images/auth-illustration.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JavaScript-Mastery-Pro/university-library/HEAD/public/images/auth-illustration.png -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "next/core-web-vitals", 4 | "next/typescript", 5 | "standard", 6 | "plugin:tailwindcss/recommended", 7 | "prettier" 8 | ] 9 | } -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | /** @type {import('postcss-load-config').Config} */ 2 | const config = { 3 | plugins: { 4 | tailwindcss: {}, 5 | }, 6 | }; 7 | 8 | export default config; 9 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /database/drizzle.ts: -------------------------------------------------------------------------------- 1 | import { neon } from "@neondatabase/serverless"; 2 | import { drizzle } from "drizzle-orm/neon-http"; 3 | 4 | import config from "@/lib/config"; 5 | 6 | const sql = neon(config.env.databaseUrl!); 7 | export const db = drizzle({ client: sql }); 8 | -------------------------------------------------------------------------------- /database/redis.ts: -------------------------------------------------------------------------------- 1 | import config from "@/lib/config"; 2 | import { Redis } from "@upstash/redis"; 3 | 4 | const redis = new Redis({ 5 | url: config.env.upstash.redisUrl!, 6 | token: config.env.upstash.redisToken!, 7 | }); 8 | 9 | export default redis; 10 | -------------------------------------------------------------------------------- /.idea/fileColors.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /.idea/jsLinters/eslint.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 7 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/prettier.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | -------------------------------------------------------------------------------- /lib/ratelimit.ts: -------------------------------------------------------------------------------- 1 | import { Ratelimit } from "@upstash/ratelimit"; 2 | 3 | import redis from "@/database/redis"; 4 | 5 | const ratelimit = new Ratelimit({ 6 | redis, 7 | limiter: Ratelimit.fixedWindow(50, "1m"), 8 | ephemeralCache: new Map(), 9 | prefix: "@upstash/ratelimit", 10 | analytics: true, 11 | }); 12 | 13 | export default ratelimit; 14 | -------------------------------------------------------------------------------- /drizzle.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from "drizzle-kit"; 2 | import { config as dotenvConfig } from "dotenv"; 3 | 4 | dotenvConfig({ path: ".env.local" }); 5 | 6 | export default defineConfig({ 7 | schema: "./database/schema.ts", 8 | out: "./migrations", 9 | dialect: "postgresql", 10 | dbCredentials: { 11 | url: process.env.DATABASE_URL!, 12 | }, 13 | }); 14 | -------------------------------------------------------------------------------- /components/admin/ErrorFallback.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | interface ErrorFallbackProps { 4 | error: Error; 5 | } 6 | 7 | const ErrorFallback = ({ error }: ErrorFallbackProps) => { 8 | return ( 9 |
10 |

Oops! Something went wrong.

11 |

{error.message}

12 |
13 | ); 14 | }; 15 | 16 | export default ErrorFallback; 17 | -------------------------------------------------------------------------------- /components/admin/UserCard.tsx: -------------------------------------------------------------------------------- 1 | import Avatar from "../Avatar"; 2 | 3 | interface Props { 4 | name: string; 5 | email: string; 6 | } 7 | 8 | const UserCard = ({ name, email }: Props) => ( 9 |
10 | 11 |

{name}

12 |

{email}

13 |
14 | ); 15 | 16 | export default UserCard; 17 | -------------------------------------------------------------------------------- /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 | 8 | export function getInitials(name: string): string { 9 | return name 10 | .split(" ") 11 | .map((word) => word[0]) 12 | .join("") 13 | .toUpperCase() 14 | .slice(0, 2); 15 | } 16 | -------------------------------------------------------------------------------- /app/api/auth/imagekit/route.ts: -------------------------------------------------------------------------------- 1 | import ImageKit from "imagekit"; 2 | import { NextResponse } from "next/server"; 3 | 4 | import config from "@/lib/config"; 5 | 6 | const imagekit = new ImageKit({ 7 | publicKey: config.env.imagekit.publicKey!, 8 | privateKey: config.env.imagekit.privateKey!, 9 | urlEndpoint: config.env.imagekit.urlEndpoint!, 10 | }); 11 | 12 | export async function GET() { 13 | return NextResponse.json(imagekit.getAuthenticationParameters()); 14 | } 15 | -------------------------------------------------------------------------------- /.idea/material_theme_project_new.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 11 | 12 | -------------------------------------------------------------------------------- /app/(auth)/sign-in/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import AuthForm from "@/components/forms/AuthForm"; 4 | 5 | import { signInSchema } from "@/lib/validations"; 6 | import { signInWithCredentials } from "@/lib/actions/auth"; 7 | 8 | const Page = () => ( 9 | 18 | ); 19 | 20 | export default Page; 21 | -------------------------------------------------------------------------------- /app/admin/books/new/page.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | 3 | import { Button } from "@/components/ui/button"; 4 | import BookForm from "@/components/admin/forms/BookForm"; 5 | 6 | const Page = () => { 7 | return ( 8 | <> 9 | 12 | 13 |
14 | 15 |
16 | 17 | ); 18 | }; 19 | 20 | export default Page; 21 | -------------------------------------------------------------------------------- /.idea/university-library.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "app/globals.css", 9 | "baseColor": "slate", 10 | "cssVariables": false, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /public/icons/home.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/(auth)/sign-up/page.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { signUp } from "@/lib/actions/auth"; 4 | import { signUpSchema } from "@/lib/validations"; 5 | 6 | import AuthForm from "@/components/forms/AuthForm"; 7 | 8 | const Page = () => ( 9 | 21 | ); 22 | 23 | export default Page; 24 | -------------------------------------------------------------------------------- /public/icons/admin/search.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /components/admin/Header.tsx: -------------------------------------------------------------------------------- 1 | import { Session } from "next-auth"; 2 | 3 | import Search from "./Search"; 4 | const Header = ({ session }: { session: Session }) => { 5 | return ( 6 |
7 |
8 |

9 | {session?.user?.name} 10 |

11 |

12 | Monitor all of your projects and tasks here 13 |

14 |
15 | 16 | 17 |
18 | ); 19 | }; 20 | 21 | export default Header; 22 | -------------------------------------------------------------------------------- /app/too-fast/page.tsx: -------------------------------------------------------------------------------- 1 | const Page = () => { 2 | return ( 3 |
4 |

5 | Whoa, Slow Down There, Speedy! 6 |

7 |

8 | Looks like you've been a little too eager. We've put a 9 | temporary pause on your excitement. 🚦 Chill for a bit, and try again 10 | shortly 11 |

12 |
13 | ); 14 | }; 15 | 16 | export default Page; 17 | -------------------------------------------------------------------------------- /public/icons/logout.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/icons/search-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "esbenp.prettier-vscode", 3 | "editor.formatOnSave": true, 4 | "editor.codeActionsOnSave": { 5 | "source.fixAll.eslint": "explicit", 6 | "source.addMissingImports": "explicit" 7 | }, 8 | "prettier.tabWidth": 2, 9 | "prettier.useTabs": false, 10 | "prettier.semi": true, 11 | "prettier.singleQuote": false, 12 | "prettier.jsxSingleQuote": false, 13 | "prettier.trailingComma": "es5", 14 | "prettier.arrowParens": "always", 15 | "[typescriptreact]": { 16 | "editor.defaultFormatter": "esbenp.prettier-vscode" 17 | } 18 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | 32 | # env files (can opt-in for committing if needed) 33 | .env* 34 | 35 | # vercel 36 | .vercel 37 | 38 | # typescript 39 | *.tsbuildinfo 40 | next-env.d.ts 41 | -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | /* config options here */ 5 | images: { 6 | remotePatterns: [ 7 | { 8 | protocol: "https", 9 | hostname: "ik.imagekit.io", 10 | port: "", 11 | }, 12 | { 13 | protocol: "https", 14 | hostname: "m.media-amazon.com", 15 | port: "", 16 | }, 17 | { 18 | protocol: "https", 19 | hostname: "placehold.co", 20 | }, 21 | ], 22 | }, 23 | typescript: { 24 | ignoreBuildErrors: true, 25 | }, 26 | eslint: { 27 | ignoreDuringBuilds: true, 28 | }, 29 | }; 30 | 31 | export default nextConfig; 32 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import path from "node:path"; 2 | import { fileURLToPath } from "node:url"; 3 | import js from "@eslint/js"; 4 | import { FlatCompat } from "@eslint/eslintrc"; 5 | 6 | const __filename = fileURLToPath(import.meta.url); 7 | const __dirname = path.dirname(__filename); 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | recommendedConfig: js.configs.recommended, 11 | allConfig: js.configs.all, 12 | }); 13 | 14 | export default [ 15 | ...compat.extends( 16 | "next/core-web-vitals", 17 | "next/typescript", 18 | "standard", 19 | "plugin:tailwindcss/recommended", 20 | "prettier" 21 | ), 22 | { 23 | rules: { 24 | "no-undef": "off", 25 | }, 26 | }, 27 | ]; 28 | -------------------------------------------------------------------------------- /public/icons/clock.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /lib/config.ts: -------------------------------------------------------------------------------- 1 | const config = { 2 | env: { 3 | imagekit: { 4 | publicKey: process.env.NEXT_PUBLIC_IMAGEKIT_PUBLIC_KEY, 5 | privateKey: process.env.IMAGEKIT_PRIVATE_KEY, 6 | urlEndpoint: process.env.NEXT_PUBLIC_IMAGEKIT_URL_ENDPOINT, 7 | }, 8 | apiEndpoint: process.env.NEXT_PUBLIC_API_ENDPOINT, 9 | prodApiEndpoint: process.env.NEXT_PUBLIC_PROD_API_ENDPOINT, 10 | databaseUrl: process.env.DATABASE_URL, 11 | upstash: { 12 | redisUrl: process.env.UPSTASH_REDIS_URL, 13 | redisToken: process.env.UPSTASH_REDIS_TOKEN, 14 | qstashUrl: process.env.QSTASH_URL, 15 | qstashToken: process.env.QSTASH_TOKEN, 16 | }, 17 | resendToken: process.env.RESEND_TOKEN, 18 | }, 19 | }; 20 | 21 | export default config; 22 | -------------------------------------------------------------------------------- /public/icons/admin/caret-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/icons/admin/caret-up.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /public/icons/admin/eye.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /migrations/meta/_journal.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "7", 3 | "dialect": "postgresql", 4 | "entries": [ 5 | { 6 | "idx": 0, 7 | "version": "7", 8 | "when": 1735823044525, 9 | "tag": "0000_worthless_invaders", 10 | "breakpoints": true 11 | }, 12 | { 13 | "idx": 1, 14 | "version": "7", 15 | "when": 1735824142407, 16 | "tag": "0001_lucky_shape", 17 | "breakpoints": true 18 | }, 19 | { 20 | "idx": 2, 21 | "version": "7", 22 | "when": 1735824235232, 23 | "tag": "0002_special_vapor", 24 | "breakpoints": true 25 | }, 26 | { 27 | "idx": 3, 28 | "version": "7", 29 | "when": 1735915636751, 30 | "tag": "0003_worthless_goblin_queen", 31 | "breakpoints": true 32 | } 33 | ] 34 | } -------------------------------------------------------------------------------- /components/BookVideo.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { IKVideo, ImageKitProvider } from "imagekitio-next"; 4 | 5 | import config from "@/lib/config"; 6 | import { cn } from "@/lib/utils"; 7 | 8 | interface Props { 9 | videoUrl: string; 10 | className?: string; 11 | } 12 | 13 | const BookVideo = ({ videoUrl, className }: Props) => { 14 | return ( 15 | 19 | 27 | 28 | ); 29 | }; 30 | 31 | export default BookVideo; 32 | -------------------------------------------------------------------------------- /app/admin/books/[id]/edit/page.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { redirect } from "next/navigation"; 3 | 4 | import { Button } from "@/components/ui/button"; 5 | import { getBook } from "@/lib/admin/actions/book"; 6 | import BookForm from "@/components/admin/forms/BookForm"; 7 | 8 | const Page = async ({ params }: PageProps) => { 9 | const { id } = await params; 10 | const { success, data: book } = await getBook({ id }); 11 | 12 | if (!success) redirect("/404"); 13 | 14 | return ( 15 | <> 16 | 19 | 20 |
21 | 22 |
23 | 24 | ); 25 | }; 26 | 27 | export default Page; 28 | -------------------------------------------------------------------------------- /public/icons/admin/plus.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | import { cva, type VariantProps } from "class-variance-authority" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | const labelVariants = cva( 10 | "text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70" 11 | ) 12 | 13 | const Label = React.forwardRef< 14 | React.ElementRef, 15 | React.ComponentPropsWithoutRef & 16 | VariantProps 17 | >(({ className, ...props }, ref) => ( 18 | 23 | )) 24 | Label.displayName = LabelPrimitive.Root.displayName 25 | 26 | export { Label } 27 | -------------------------------------------------------------------------------- /components/ui/textarea.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | const Textarea = React.forwardRef< 6 | HTMLTextAreaElement, 7 | React.ComponentProps<"textarea"> 8 | >(({ className, ...props }, ref) => { 9 | return ( 10 |