This commit is contained in:
NerfedJabolo 2024-06-21 15:03:09 +03:00
commit b919dcab78
29 changed files with 1580 additions and 100 deletions

7
.env.example Normal file
View file

@ -0,0 +1,7 @@
MONGO_URI=
UPLOADTHING_SECRET=
UPLOADTHING_APP_ID=
ADMIN_USERNAME=
ADMIN_PASSWORD=

9
actions/login.action.ts Normal file
View file

@ -0,0 +1,9 @@
"use server";
export function CheckLoggedIn(username: string, password: string) {
if (
username === process.env.ADMIN_USERNAME &&
password === process.env.ADMIN_PASSWORD
) {
}
}

View file

@ -0,0 +1,23 @@
import { ArrowUpRight } from "lucide-react";
import Link from "next/link";
import { FC } from "react";
interface CardProps {
title: string;
url: string;
}
const Card: FC<CardProps> = ({ title, url }) => {
return (
<div className=" bg-zinc-900 border rounded-lg hover:bg-zinc-800 transition-all">
<Link href={url} className="group">
<div className="flex-center rounded-lg p-6">
<h2 className="text-2xl font-bold mb-2">{title}</h2>
<ArrowUpRight className="w-5 h-5 mb-2 ml-2 opacity-0 group-hover:opacity-100 transition-all duration-300" />
</div>
</Link>
</div>
);
};
export default Card;

View file

@ -0,0 +1,56 @@
"use client";
import React from "react";
import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet";
import Link from "next/link";
import Image from "next/image";
import { AdminLinks } from "@/constants";
import { usePathname } from "next/navigation";
import { Menu } from "lucide-react";
const MobileNav = () => {
const pathname = usePathname();
return (
<header className="header">
<Link href="/" className="flex items-center gap-2 md:py-2">
<Image src="/logo.svg" alt="" width={180} height={28} />
</Link>
<nav className="flex gap-2">
<Sheet>
<SheetTrigger>
<Menu className="w-5 h-5" />
</SheetTrigger>
<SheetContent className="sheet-content sm:w-64">
<>
<Image src="/logo.svg" alt="" width={152} height={23} />
<ul className="header-nav_elements">
{AdminLinks.slice(0, 6).map((link) => {
const isActive = link.url === pathname;
return (
<li
key={link.url}
className={`${
isActive && "gradient-text"
} p-18 flex whitespace-nowrap text-dark-700`}
>
<Link
className="sidebar-link cursor-pointer"
href={link.url}
>
<link.icon />
{link.name}
</Link>
</li>
);
})}
</ul>
</>
</SheetContent>
</Sheet>
</nav>
</header>
);
};
export default MobileNav;

View file

@ -0,0 +1,65 @@
"use client";
import Link from "next/link";
import Image from "next/image";
import { usePathname } from "next/navigation";
import { AdminLinks } from "@/constants";
const Sidebar = () => {
const pathname = usePathname();
return (
<>
<aside className="sidebar">
<div className="flex size-full flex-col gap-4">
<Link href="/" className="sidebar-logo">
<Image src="/logo.svg" alt="" width={180} height={28} />
</Link>
<nav className="sidebar-nav">
<ul className="sidebar-nav_elements">
{AdminLinks.slice(0, 4).map((link) => {
const isActive = link.url === pathname;
return (
<li
key={link.url}
className={`sidebar-nav_element group ${
isActive ? "bg-white/5" : "text-gray-700"
}`}
>
<Link className="sidebar-link" href={link.url}>
<link.icon />
{link.name}
</Link>
</li>
);
})}
</ul>
<ul className="sidebar-nav_elements">
{AdminLinks.slice(4).map((link) => {
const isActive = link.url === pathname;
return (
<li
key={link.url}
className={`sidebar-nav_element group ${
isActive ? "bg-purple-gradient" : "text-gray-700"
}`}
>
<Link className="sidebar-link" href={link.url}>
<link.icon />
{link.name}
</Link>
</li>
);
})}
</ul>
</nav>
</div>
</aside>
</>
);
};
export default Sidebar;

View file

@ -0,0 +1,125 @@
"use client";
import React from "react";
import { useForm, SubmitHandler } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { z } from "zod";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { UploadButton, UploadDropzone } from "@/lib/uploadthing";
import { downloadSchema } from "@/lib/validations/validation";
const AdminPage = () => {
const form = useForm<z.infer<typeof downloadSchema>>({
resolver: zodResolver(downloadSchema),
});
const onSubmit: SubmitHandler<z.infer<typeof downloadSchema>> = async (
data
) => {
const response = await fetch("/api/upload", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
if (response.ok) {
form.reset();
console.log("Upload successful");
alert("Uploaded");
} else {
console.error("Upload failed");
alert("Upload Failed");
}
};
return (
<section id="admin-page" className="wrapper container">
<h1 className="text-3xl font-bold py-6">Admin Upload Section</h1>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="fileName"
render={({ field }) => (
<FormItem>
<FormLabel>File Name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="version"
render={({ field }) => (
<FormItem>
<FormLabel>Version</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="downloadLink"
render={({ field }) => (
<FormItem>
<FormLabel>Download Link</FormLabel>
<UploadButton
endpoint="imageUploader"
onClientUploadComplete={(res) => {
field.onChange(res[0].url);
}}
onUploadError={(error: Error) => {
alert(`ERROR! ${error.message}`);
}}
/>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="fileSize"
render={({ field }) => (
<FormItem>
<FormLabel>File Size</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button
type="submit"
className="w-full text-lg rounded-full"
size={"lg"}
>
Submit
</Button>
</form>
</Form>
</section>
);
};
export default AdminPage;

21
app/(auth)/admin/page.tsx Normal file
View file

@ -0,0 +1,21 @@
import React from "react";
import Card from "../_components/Card";
const AdminPage = () => {
return (
<>
<section id="adminpage" className="wrapper container">
<h1 className="h2-bold py-6">Admin Page</h1>
<div className="grid lg:grid-cols-2 grid-cols-1 gap-4 ">
<Card title="Downloads" url="/admin/downloads" />
<Card title="Mods" url="/admin" />
<Card title="Logs" url="/admin" />
<Card title="Docs" url="/admin" />
</div>
</section>
</>
);
};
export default AdminPage;

16
app/(auth)/layout.tsx Normal file
View file

@ -0,0 +1,16 @@
import MobileNav from "./_components/Mobilenav";
import Sidebar from "./_components/Sidebar";
export default function PageLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<main className="flex flex-col min-h-screen root">
<Sidebar />
<MobileNav />
<div className="flex-grow flex-1 root-container">{children}</div>
</main>
);
}

View file

@ -1,10 +1,12 @@
import { Button } from '@/components/ui/button';
"use client";
import { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import {
Table,
TableBody,
TableCaption,
TableCell,
TableFooter,
TableHead,
TableHeader,
TableRow,
@ -12,38 +14,45 @@ import {
import { Download } from 'lucide-react';
import Link from 'next/link';
const downloads = [
{
date: '2024-06-01',
fileName: 'SVRJS_v1.0.0.zip',
version: '1.0.0',
fileSize: '15MB',
downloadLink: '/downloads/SVRJS_v1.0.0.zip',
},
{
date: '2024-06-10',
fileName: 'SVRJS_v1.1.0.zip',
version: '1.1.0',
fileSize: '18MB',
downloadLink: '/downloads/SVRJS_v1.1.0.zip',
},
{
date: '2024-06-15',
fileName: 'SVRJS_v1.2.0.zip',
version: '1.2.0',
fileSize: '20MB',
downloadLink: '/downloads/SVRJS_v1.2.0.zip',
},
{
date: '2024-06-20',
fileName: 'SVRJS_v1.3.0.zip',
version: '1.3.0',
fileSize: '22MB',
downloadLink: '/downloads/SVRJS_v1.3.0.zip',
},
];
interface Download {
_id: string;
date: string;
fileName: string;
version: string;
fileSize: string;
downloadLink: string;
}
const DownloadPage: React.FC = () => {
const [downloads, setDownloads] = useState<Download[]>([]);
const [error, setError] = useState("");
const fetchDownloads = async () => {
try {
const response = await fetch("/api/downloads", {
method: "GET",
});
if (response.ok) {
const data: Download[] = await response.json();
setDownloads(data);
} else {
throw new Error(`HTTP error! status: ${response.status}`);
}
} catch (error: any) {
setError(error);
}
};
useEffect(() => {
fetchDownloads();
const interval = setInterval(() => {
fetchDownloads();
}, 10000);
return () => clearInterval(interval);
}, []);
const DownloadPage = () => {
return (
<section
id="download"
@ -55,6 +64,7 @@ const DownloadPage = () => {
<p className="text-lg text-muted-foreground text-start mb-4">
Get all the latest version of SVRJS download and compiled Files here!
</p>
{error && <p className="text-red-500">{error}</p>}
<Table>
<TableCaption>A list of all available downloads.</TableCaption>
<TableHeader>
@ -62,23 +72,23 @@ const DownloadPage = () => {
<TableHead className="w-[150px]">Date</TableHead>
<TableHead>File Name</TableHead>
<TableHead>Version</TableHead>
<TableHead>Download Link</TableHead>
<TableHead className="text-right">File Size</TableHead>
<TableHead>File Size</TableHead>
<TableHead className="text-right">Download Link</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{downloads
.slice()
.slice(0, 10)
.reverse()
.map((download) => (
<TableRow key={download.fileName}>
<TableRow key={download._id}>
<TableCell className="font-medium">{download.date}</TableCell>
<TableCell>{download.fileName}</TableCell>
<TableCell>{download.version}</TableCell>
<TableCell className="text-left">{download.fileSize}</TableCell>
<TableCell className="flex items-center justify-end">
<Link href={download.downloadLink}>
<Button variant={'ghost'} className="">
<Button variant={"ghost"} className="">
<Download className="w-4 h-4 mr-2" />
Download
</Button>

16
app/(root)/layout.tsx Normal file
View file

@ -0,0 +1,16 @@
import Footer from "@/components/shared/Footer";
import Navbar from "@/components/shared/Navbar";
export default function PageLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<main className="flex flex-col min-h-screen">
<Navbar />
<div className="flex-grow flex-1">{children}</div>
<Footer />
</main>
);
}

View file

@ -0,0 +1,17 @@
import { NextRequest, NextResponse } from "next/server";
import clientPromise from "@/lib/db";
// Handler for GET requests
export async function GET(req: NextRequest) {
try {
const client = await clientPromise;
const db = client.db("downloadsDatabase");
const downloads = await db.collection("downloads").find().toArray();
return NextResponse.json(downloads, { status: 200 });
} catch (error) {
return NextResponse.json(
{ error: "Failed to fetch downloads" },
{ status: 500 }
);
}
}

View file

@ -1,31 +1,31 @@
// import { NextResponse } from 'next/server';
// import { setCookie } from 'nookies';
// require('dotenv').config();
import { NextRequest, NextResponse } from "next/server";
import { serialize } from "cookie";
// // nvm, clerk is overkill for one u
export async function POST(request: NextRequest) {
const { username, password } = await request.json();
// export async function POST(request: NextApiRequest) {
// const { username, password } = await request.json();
// console.log(username, password);
// console.log(process.env.PASSWORD);
const adminUsername = process.env.ADMIN_USERNAME;
const adminPassword = process.env.ADMIN_PASSWORD;
// if (username === process.env.USERNAME && password === process.env.PASSWORD) {
// const response = NextResponse.json(
// { message: 'Login successful' },
// { status: 200 }
// );
if (username === adminUsername && password === adminPassword) {
const cookie = serialize("auth", "authenticated", {
httpOnly: true,
path: "/",
maxAge: 60 * 60 * 24, // 1 day
});
// setCookie({ res: response }, 'token', 'your-auth-token', {
// httpOnly: true,
// secure: process.env.NODE_ENV !== 'development',
// maxAge: 30 * 24 * 60 * 60,
// path: '/',
// });
return new NextResponse(JSON.stringify({ message: "Login successful" }), {
headers: {
"Set-Cookie": cookie,
"Content-Type": "application/json",
},
});
}
// return response;
// } else {
// return NextResponse.json({ message: 'Login failed' }, { status: 401 });
// }
// }
// im gonna create server actions
return new NextResponse(JSON.stringify({ message: "Invalid credentials" }), {
status: 401,
headers: {
"Content-Type": "application/json",
},
});
}

20
app/api/upload/route.ts Normal file
View file

@ -0,0 +1,20 @@
import { NextResponse } from "next/server";
import clientPromise from "@/lib/db";
export async function POST(request: Request) {
const body = await request.json();
const { fileName, version, downloadLink, fileSize } = body;
const client = await clientPromise;
const db = client.db("downloadsDatabase");
const result = await db.collection("downloads").insertOne({
date: new Date().toISOString().split("T")[0],
fileName,
version,
downloadLink,
fileSize,
});
return NextResponse.json({ success: true, id: result.insertedId });
}

View file

@ -0,0 +1,22 @@
import { createUploadthing, type FileRouter } from "uploadthing/next";
import { UploadThingError } from "uploadthing/server";
const f = createUploadthing();
// const auth = (req: Request) => ({ id: "fakeId" });
export const ourFileRouter = {
imageUploader: f({ "application/zip": { maxFileSize: "8MB" } })
// .middleware(async ({ req }) => {
// const user = await auth(req);
// if (!user) throw new UploadThingError("Unauthorized");
// return { userId: user.id };
// })
.onUploadComplete(async ({ metadata, file }) => {
// console.log("Upload complete for userId:", metadata.userId);
console.log("file url", file.url);
// return { uploadedBy: metadata.userId };
}),
} satisfies FileRouter;
export type OurFileRouter = typeof ourFileRouter;

View file

@ -0,0 +1,6 @@
import { createRouteHandler } from "uploadthing/next";
import { ourFileRouter } from "./core";
export const { GET, POST } = createRouteHandler({
router: ourFileRouter,
});

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 15 KiB

View file

@ -99,6 +99,54 @@ body {
.flex-end {
@apply flex justify-between items-end;
}
.root {
@apply flex max-h-screen w-full flex-col lg:flex-row;
}
.root-container {
@apply mt-16 flex-1 overflow-auto py-8 lg:mt-0 lg:max-h-screen lg:py-10;
}
/* .gradient-text {
@apply bg-purple-gradient bg-cover bg-clip-text text-transparent;
} */
.sheet-content button {
@apply focus:ring-0 focus-visible:ring-transparent focus:ring-offset-0 focus-visible:ring-offset-0 focus-visible:outline-none focus-visible:border-none !important;
}
.sidebar {
@apply hidden h-screen w-72 p-5 shadow-md shadow-purple-200/50 lg:flex;
}
.header {
@apply flex-between fixed h-16 w-full border-b p-5 lg:hidden;
}
.header-nav_elements {
@apply mt-8 flex w-full flex-col items-start gap-5;
}
/* Search Component */
.search {
@apply flex w-full rounded-[16px] border-2 border-purple-200/20 bg-white px-4 shadow-sm shadow-purple-200/15 md:max-w-96;
}
.sidebar-logo {
@apply flex items-center gap-2 md:py-2;
}
.sidebar-nav {
@apply h-full flex-col justify-between md:flex md:gap-4;
}
.sidebar-nav_elements {
@apply hidden w-full flex-col items-start gap-2 md:flex;
}
.sidebar-nav_element {
@apply flex-center p-medium-16 w-full whitespace-nowrap rounded-full bg-cover transition-all hover:bg-white/10 hover:shadow-inner;
}
.sidebar-link {
@apply flex p-medium-16 size-full gap-4 p-4;
}
/* TYPOGRAPHY */
/* 64 */

View file

@ -1,9 +1,7 @@
import type { Metadata } from 'next';
import { Poppins } from 'next/font/google';
import './globals.css';
import { ThemeProvider } from '@/components/shared/providers/themeprovider';
import Navbar from '@/components/shared/Navbar';
import Footer from '@/components/shared/Footer';
import type { Metadata } from "next";
import { Poppins } from "next/font/google";
import "./globals.css";
import { ThemeProvider } from "@/components/shared/providers/themeprovider";
const poppins = Poppins({
weight: ['400', '600', '700', '900'],
@ -22,18 +20,14 @@ export default function RootLayout({
}>) {
return (
<html lang="en" suppressHydrationWarning>
<body className={`font-sans antialiased ${poppins.className}`}>
<body className={`antialiased ${poppins.className}`}>
<ThemeProvider
attribute="class"
defaultTheme="dark"
enableSystem
disableTransitionOnChange
>
<main className="flex flex-col min-h-screen">
<Navbar />
<div className="flex-grow flex-1">{children}</div>
<Footer />
</main>
{children}
</ThemeProvider>
</body>
</html>

64
app/login/page.tsx Normal file
View file

@ -0,0 +1,64 @@
"use client";
import React, { useState } from "react";
import { useRouter } from "next/navigation";
import { Button } from "@/components/ui/button";
const LoginPage = () => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const router = useRouter();
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
const response = await fetch("/api/login", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ username, password }),
});
if (response.ok) {
router.push("/admin");
} else {
const data = await response.json();
setError(data.message);
}
};
return (
<div className="max-w-xl h-screen flex justify-center bg-gray-900 flex-col container">
<h1 className="text-3xl font-bold mb-4">SVRJS ADMIN PANEL</h1>
{error && <p className="text-red-500 mb-4">{error}</p>}
<form onSubmit={handleLogin}>
<div className="mb-4">
<input
type="text"
id="username"
placeholder="Username"
value={username}
onChange={(e) => setUsername(e.target.value)}
className="mt-1 block w-full bg-gray-800 rounded-full px-5 py-2 shadow-sm p-2"
/>
</div>
<div className="mb-4">
<input
type="password"
placeholder="Password"
id="password"
value={password}
onChange={(e) => setPassword(e.target.value)}
className="mt-1 block w-full bg-gray-800 rounded-full px-5 py-2 shadow-sm"
/>
</div>
<Button type="submit" className="w-full rounded-full" size={"lg"}>
Login
</Button>
</form>
</div>
);
};
export default LoginPage;

176
components/ui/form.tsx Normal file
View file

@ -0,0 +1,176 @@
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { Slot } from "@radix-ui/react-slot"
import {
Controller,
ControllerProps,
FieldPath,
FieldValues,
FormProvider,
useFormContext,
} from "react-hook-form"
import { cn } from "@/lib/utils"
import { Label } from "@/components/ui/label"
const Form = FormProvider
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
> = {
name: TName
}
const FormFieldContext = React.createContext<FormFieldContextValue>(
{} as FormFieldContextValue
)
const FormField = <
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>
>({
...props
}: ControllerProps<TFieldValues, TName>) => {
return (
<FormFieldContext.Provider value={{ name: props.name }}>
<Controller {...props} />
</FormFieldContext.Provider>
)
}
const useFormField = () => {
const fieldContext = React.useContext(FormFieldContext)
const itemContext = React.useContext(FormItemContext)
const { getFieldState, formState } = useFormContext()
const fieldState = getFieldState(fieldContext.name, formState)
if (!fieldContext) {
throw new Error("useFormField should be used within <FormField>")
}
const { id } = itemContext
return {
id,
name: fieldContext.name,
formItemId: `${id}-form-item`,
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
}
}
type FormItemContextValue = {
id: string
}
const FormItemContext = React.createContext<FormItemContextValue>(
{} as FormItemContextValue
)
const FormItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => {
const id = React.useId()
return (
<FormItemContext.Provider value={{ id }}>
<div ref={ref} className={cn("space-y-2", className)} {...props} />
</FormItemContext.Provider>
)
})
FormItem.displayName = "FormItem"
const FormLabel = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
>(({ className, ...props }, ref) => {
const { error, formItemId } = useFormField()
return (
<Label
ref={ref}
className={cn(error && "text-destructive", className)}
htmlFor={formItemId}
{...props}
/>
)
})
FormLabel.displayName = "FormLabel"
const FormControl = React.forwardRef<
React.ElementRef<typeof Slot>,
React.ComponentPropsWithoutRef<typeof Slot>
>(({ ...props }, ref) => {
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
return (
<Slot
ref={ref}
id={formItemId}
aria-describedby={
!error
? `${formDescriptionId}`
: `${formDescriptionId} ${formMessageId}`
}
aria-invalid={!!error}
{...props}
/>
)
})
FormControl.displayName = "FormControl"
const FormDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => {
const { formDescriptionId } = useFormField()
return (
<p
ref={ref}
id={formDescriptionId}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
)
})
FormDescription.displayName = "FormDescription"
const FormMessage = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, children, ...props }, ref) => {
const { error, formMessageId } = useFormField()
const body = error ? String(error?.message) : children
if (!body) {
return null
}
return (
<p
ref={ref}
id={formMessageId}
className={cn("text-sm font-medium text-destructive", className)}
{...props}
>
{body}
</p>
)
})
FormMessage.displayName = "FormMessage"
export {
useFormField,
Form,
FormItem,
FormLabel,
FormControl,
FormDescription,
FormMessage,
FormField,
}

26
components/ui/label.tsx Normal file
View file

@ -0,0 +1,26 @@
"use client"
import * as React from "react"
import * as LabelPrimitive from "@radix-ui/react-label"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
)
const Label = React.forwardRef<
React.ElementRef<typeof LabelPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root> &
VariantProps<typeof labelVariants>
>(({ className, ...props }, ref) => (
<LabelPrimitive.Root
ref={ref}
className={cn(labelVariants(), className)}
{...props}
/>
))
Label.displayName = LabelPrimitive.Root.displayName
export { Label }

View file

@ -1,4 +1,5 @@
import { BadgeAlert, BarChart4, Cog, ShieldCheck } from "lucide-react";
import { Download, Home, Settings, User } from "lucide-react";
export const NAVBAR = {
centerLinks: [
@ -95,7 +96,7 @@ export const questions = [
key: "item-3",
question: "How was SVR.JS created?",
answer:
"Someone in 2018 wanted to create a website, but he didn't know about setting up popular web server software like Apache httpd, NGINX, or IIS... So he created his own web server in Node.JS to serve his website! And he saved it in a file called svr.js. Since then, this web server has been gradually turned from a web server intended for one website into a general-purpose web server, which is what SVR.JS is today!",
"Someone in 2018 wanted to create a website, but he didnt know about setting up popular web server software like Apache httpd, NGINX, or IIS... So he created his own web server in Node.JS to serve his website! And he saved it in a file called svr.js. Since then, this web server has been gradually turned from a web server intended for one website into a general-purpose web server, which is what SVR.JS is today!",
},
{
key: "item-4",
@ -107,7 +108,7 @@ export const questions = [
key: "item-5",
question: "What is Node.JS?",
answer:
"Node.JS is an asynchronous event-driven JavaScript runtime built on Chromium's V8 engine. Node.JS is designed to build scalable network applications.",
"Node.JS is an asynchronous event-driven JavaScript runtime built on Chromiums V8 engine. Node.JS is designed to build scalable network applications.",
},
{
key: "item-6",
@ -145,6 +146,34 @@ export const FOOTERLINKS = {
},
};
export const AdminLinks = [
{
name: "Dashboard",
url: "/admin",
icon: Home,
},
{
name: "Downloads",
url: "/admin/downloads",
icon: Download,
},
{
name: "Mods",
url: "/admin/mods",
icon: User,
},
{
name: "Logs",
url: "/admin/changelogs",
icon: Settings,
},
{
name: "Back Home",
url: "/",
icon: Home,
},
];
export const TERMS_AND_CONDITIONS = `
Last updated: 24.04.2024

24
lib/db.ts Normal file
View file

@ -0,0 +1,24 @@
import { MongoClient } from "mongodb";
if (!process.env.MONGODB_URI) {
throw new Error('Invalid/Missing environment variable: "MONGODB_URI"');
}
const uri = process.env.MONGODB_URI;
const options = {};
let client;
let clientPromise: Promise<MongoClient>;
if (process.env.NODE_ENV === "development") {
if (!(global as any)._mongoClientPromise) {
client = new MongoClient(uri!, options);
(global as any)._mongoClientPromise = client.connect();
}
clientPromise = (global as any)._mongoClientPromise;
} else {
client = new MongoClient(uri!, options);
clientPromise = client.connect();
}
export default clientPromise;

9
lib/uploadthing.ts Normal file
View file

@ -0,0 +1,9 @@
import {
generateUploadButton,
generateUploadDropzone,
} from "@uploadthing/react";
import type { OurFileRouter } from "@/app/api/uploadthing/core";
export const UploadButton = generateUploadButton<OurFileRouter>();
export const UploadDropzone = generateUploadDropzone<OurFileRouter>();

View file

@ -0,0 +1,8 @@
import { z } from "zod";
export const downloadSchema = z.object({
fileName: z.string().nonempty(),
version: z.string().nonempty(),
downloadLink: z.string().url().nonempty(),
fileSize: z.string().nonempty(),
});

21
middleware.ts Normal file
View file

@ -0,0 +1,21 @@
import { NextResponse } from "next/server";
import type { NextRequest } from "next/server";
export function middleware(req: NextRequest) {
const url = req.nextUrl.clone();
if (url.pathname.startsWith("/admin")) {
const authCookie = req.cookies.get("auth");
if (!authCookie) {
url.pathname = "/login";
return NextResponse.redirect(url);
}
}
return NextResponse.next();
}
export const config = {
matcher: ["/admin/:path*"],
};

698
package-lock.json generated
View file

@ -11,24 +11,33 @@
"@fortawesome/fontawesome-svg-core": "^6.5.2",
"@fortawesome/free-regular-svg-icons": "^6.5.2",
"@fortawesome/react-fontawesome": "^0.2.2",
"@hookform/resolvers": "^3.6.0",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-navigation-menu": "^1.1.4",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/themes": "^3.0.5",
"@types/cookie": "^0.6.0",
"@uploadthing/react": "^6.6.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"cookie": "^0.6.0",
"framer-motion": "^11.2.10",
"lucide-react": "^0.394.0",
"mini-svg-data-uri": "^1.4.4",
"mongoose": "^8.4.3",
"next": "14.2.3",
"next-themes": "^0.3.0",
"react": "^18",
"react-dom": "^18",
"react-fontawesome": "^1.7.1",
"react-hook-form": "^7.52.0",
"tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7"
"tailwindcss-animate": "^1.0.7",
"uploadthing": "^6.12.0",
"zod": "^3.23.8"
},
"devDependencies": {
"@types/node": "^20",
@ -63,6 +72,16 @@
"node": ">=6.9.0"
}
},
"node_modules/@effect/schema": {
"version": "0.66.16",
"resolved": "https://registry.npmjs.org/@effect/schema/-/schema-0.66.16.tgz",
"integrity": "sha512-sT/k5NOgKslGPzs3DUaCFuM6g2JQoIIT8jpwEorAZooplPIMK2xIspr7ECz6pp6Dc7Wz/ppXGk7HVyGZQsIYEQ==",
"license": "MIT",
"peerDependencies": {
"effect": "^3.1.3",
"fast-check": "^3.13.2"
}
},
"node_modules/@eslint-community/eslint-utils": {
"version": "4.4.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
@ -198,6 +217,15 @@
"react": ">=16.3"
}
},
"node_modules/@hookform/resolvers": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.6.0.tgz",
"integrity": "sha512-UBcpyOX3+RR+dNnqBd0lchXpoL8p4xC21XP8H6Meb8uve5Br1GCnmg0PcBoKKqPKgGu9GHQ/oygcmPrQhetwqw==",
"license": "MIT",
"peerDependencies": {
"react-hook-form": "^7.0.0"
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.14",
"resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
@ -317,6 +345,15 @@
"@jridgewell/sourcemap-codec": "^1.4.14"
}
},
"node_modules/@mongodb-js/saslprep": {
"version": "1.1.7",
"resolved": "https://registry.npmjs.org/@mongodb-js/saslprep/-/saslprep-1.1.7.tgz",
"integrity": "sha512-dCHW/oEX0KJ4NjDULBo3JiOaK5+6axtpBbS+ao2ZInoAL9/YRQLhXzSNAFz7hP4nzLkIqsfYAK/PDE3+XHny0Q==",
"license": "MIT",
"dependencies": {
"sparse-bitfield": "^3.0.3"
}
},
"node_modules/@next/env": {
"version": "14.2.3",
"resolved": "https://registry.npmjs.org/@next/env/-/env-14.2.3.tgz",
@ -611,6 +648,25 @@
}
}
},
"node_modules/@radix-ui/react-alert-dialog/node_modules/@radix-ui/react-slot": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
"integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-compose-refs": "1.0.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-arrow": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz",
@ -770,6 +826,25 @@
}
}
},
"node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
"integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-compose-refs": "1.0.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-compose-refs": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz",
@ -869,6 +944,25 @@
}
}
},
"node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
"integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-compose-refs": "1.0.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-direction": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.1.tgz",
@ -1012,6 +1106,30 @@
}
}
},
"node_modules/@radix-ui/react-form/node_modules/@radix-ui/react-label": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.0.2.tgz",
"integrity": "sha512-N5ehvlM7qoTLx7nWPodsPYPgMzA5WM8zZChQg8nyFJKnDO5WHdba1vv5/H6IO5LtJMfD2Q3wh1qHFGNtK0w3bQ==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-primitive": "1.0.3"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-hover-card": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/@radix-ui/react-hover-card/-/react-hover-card-1.0.7.tgz",
@ -1062,18 +1180,41 @@
}
},
"node_modules/@radix-ui/react-label": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.0.2.tgz",
"integrity": "sha512-N5ehvlM7qoTLx7nWPodsPYPgMzA5WM8zZChQg8nyFJKnDO5WHdba1vv5/H6IO5LtJMfD2Q3wh1qHFGNtK0w3bQ==",
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.0.tgz",
"integrity": "sha512-peLblDlFw/ngk3UWq0VnYaOLy6agTZZ+MUO/WhVfm14vJGML+xH4FAl2XQGLqdefjNb7ApRg6Yn7U42ZhmYXdw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-primitive": "1.0.3"
"@radix-ui/react-primitive": "2.0.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0",
"react-dom": "^16.8 || ^17.0 || ^18.0"
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-label/node_modules/@radix-ui/react-primitive": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.0.0.tgz",
"integrity": "sha512-ZSpFm0/uHa8zTvKBDjLFWLo8dkr4MBsiDLz0g3gMUwqgLHz9rTaRRGYDgvZPtBJgYCBKXkS9fzmoySgr8CO6Cw==",
"license": "MIT",
"dependencies": {
"@radix-ui/react-slot": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
@ -1124,6 +1265,25 @@
}
}
},
"node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
"integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-compose-refs": "1.0.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-navigation-menu": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.1.4.tgz",
@ -1197,6 +1357,25 @@
}
}
},
"node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
"integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-compose-refs": "1.0.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-popper": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.3.tgz",
@ -1299,6 +1478,25 @@
}
}
},
"node_modules/@radix-ui/react-primitive/node_modules/@radix-ui/react-slot": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
"integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-compose-refs": "1.0.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-progress": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.0.3.tgz",
@ -1460,6 +1658,25 @@
}
}
},
"node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
"integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-compose-refs": "1.0.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-slider": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slider/-/react-slider-1.1.2.tgz",
@ -1494,16 +1711,31 @@
}
},
"node_modules/@radix-ui/react-slot": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
"integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==",
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.1.0.tgz",
"integrity": "sha512-FUCf5XMfmW4dtYl69pdS4DbxKy8nj4M7SafBgPllysxmdachynNflAdp/gCsnYWNDnge6tI9onzMp5ARYc1KNw==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-compose-refs": "1.0.1"
"@radix-ui/react-compose-refs": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0"
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-slot/node_modules/@radix-ui/react-compose-refs": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.0.tgz",
"integrity": "sha512-b4inOtiaOnYf9KWyO3jAeeCG6FeyfY6ldiEPanbUjWd+xIk5wZeHa8yVwmrJ2vderhu/BQvzCrJI0lHd+wIiqw==",
"license": "MIT",
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
@ -1658,6 +1890,25 @@
}
}
},
"node_modules/@radix-ui/react-tooltip/node_modules/@radix-ui/react-slot": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz",
"integrity": "sha512-YeTpuq4deV+6DusvVUW4ivBgnkHwECUu0BiN43L5UCDFgdhsRUWAghhTF5MbvNTPzmiFOx90asDSUjWuCNapwg==",
"license": "MIT",
"dependencies": {
"@babel/runtime": "^7.13.10",
"@radix-ui/react-compose-refs": "1.0.1"
},
"peerDependencies": {
"@types/react": "*",
"react": "^16.8 || ^17.0 || ^18.0"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-use-callback-ref": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz",
@ -1909,6 +2160,11 @@
"tslib": "^2.4.0"
}
},
"node_modules/@types/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA=="
},
"node_modules/@types/json5": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
@ -1949,6 +2205,21 @@
"@types/react": "*"
}
},
"node_modules/@types/webidl-conversions": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/@types/webidl-conversions/-/webidl-conversions-7.0.3.tgz",
"integrity": "sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==",
"license": "MIT"
},
"node_modules/@types/whatwg-url": {
"version": "11.0.5",
"resolved": "https://registry.npmjs.org/@types/whatwg-url/-/whatwg-url-11.0.5.tgz",
"integrity": "sha512-coYR071JRaHa+xoEvvYqvnIHaVqaYrLPbsufM9BF63HkwI5Lgmy2QR8Q5K/lYDYo5AK82wOvSOS0UsLTpTG7uQ==",
"license": "MIT",
"dependencies": {
"@types/webidl-conversions": "*"
}
},
"node_modules/@typescript-eslint/parser": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.2.0.tgz",
@ -2082,6 +2353,74 @@
"integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
"dev": true
},
"node_modules/@uploadthing/dropzone": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/@uploadthing/dropzone/-/dropzone-0.4.1.tgz",
"integrity": "sha512-RHSpo/2kg/mrRSYQA4EKlyvkOCYWOeE2+QQYW9YiUvWCuawnTfD7DQvk8RN/nYXi1Sw7/v0NegmQpiVELVGtnA==",
"license": "MIT",
"dependencies": {
"file-selector": "^0.6.0"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0",
"solid-js": "^1.7.11",
"svelte": "^4.2.12",
"vue": "^3.4.0"
},
"peerDependenciesMeta": {
"react": {
"optional": true
},
"solid-js": {
"optional": true
},
"svelte": {
"optional": true
},
"vue": {
"optional": true
}
}
},
"node_modules/@uploadthing/mime-types": {
"version": "0.2.10",
"resolved": "https://registry.npmjs.org/@uploadthing/mime-types/-/mime-types-0.2.10.tgz",
"integrity": "sha512-kz3F0oEgAyts25NAGXlUBCWh3mXonbSOQJFGFMawHuIgbUbnzXbe4w5WI+0XdneCbjNmikfWrdWrs8m/7HATfQ==",
"license": "MIT"
},
"node_modules/@uploadthing/react": {
"version": "6.6.0",
"resolved": "https://registry.npmjs.org/@uploadthing/react/-/react-6.6.0.tgz",
"integrity": "sha512-jLN4Oy21d0n8F6CNPl9qjEu0/Q1rnddSxny/02Lm89L/sYuR4RXfk1vBgBGPAGXBak0BO9z5eEmYURLrXXUAJQ==",
"license": "MIT",
"dependencies": {
"@uploadthing/dropzone": "0.4.1",
"@uploadthing/shared": "6.7.5",
"file-selector": "^0.6.0",
"tailwind-merge": "^2.2.1"
},
"peerDependencies": {
"next": "*",
"react": "^17.0.2 || ^18.0.0",
"uploadthing": "6.12.0"
},
"peerDependenciesMeta": {
"next": {
"optional": true
}
}
},
"node_modules/@uploadthing/shared": {
"version": "6.7.5",
"resolved": "https://registry.npmjs.org/@uploadthing/shared/-/shared-6.7.5.tgz",
"integrity": "sha512-BZXzvh6zGEt4ip//mxfXdRTNWYw9XJ6tommL6A1TEo2l8jvdNbUpPUwXnMVWBMwio2b48BO7D9V3siYIKMD4pg==",
"license": "MIT",
"dependencies": {
"@uploadthing/mime-types": "0.2.10",
"effect": "^3.1.0",
"std-env": "^3.7.0"
}
},
"node_modules/acorn": {
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
@ -2436,6 +2775,15 @@
"node": ">=8"
}
},
"node_modules/bson": {
"version": "6.7.0",
"resolved": "https://registry.npmjs.org/bson/-/bson-6.7.0.tgz",
"integrity": "sha512-w2IquM5mYzYZv6rs3uN2DZTOBe2a0zXLj53TGDqwF4l6Sz/XsISrisXOJihArF9+BZ6Cq/GjVht7Sjfmri7ytQ==",
"license": "Apache-2.0",
"engines": {
"node": ">=16.20.1"
}
},
"node_modules/busboy": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz",
@ -2620,6 +2968,23 @@
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"dev": true
},
"node_modules/consola": {
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/consola/-/consola-3.2.3.tgz",
"integrity": "sha512-I5qxpzLv+sJhTVEoLYNcTW+bThDCPsit0vLNKShZx6rLtpilNpmmeTPaeqJb9ZE9dV3DGaeby6Vuhrw38WjeyQ==",
"license": "MIT",
"engines": {
"node": "^14.18.0 || >=16.10.0"
}
},
"node_modules/cookie": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz",
"integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@ -2711,7 +3076,6 @@
"version": "4.3.5",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.5.tgz",
"integrity": "sha512-pt0bNEmneDIvdL1Xsd9oDQ/wrQRkXDT4AUWlNZNPKvW5x/jyO9VFXkJUP07vQ2upmw5PlaITaPKc31jK13V+jg==",
"dev": true,
"dependencies": {
"ms": "2.1.2"
},
@ -2817,6 +3181,12 @@
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="
},
"node_modules/effect": {
"version": "3.3.5",
"resolved": "https://registry.npmjs.org/effect/-/effect-3.3.5.tgz",
"integrity": "sha512-ehDpb+3zRTYsCqDWY32rT4oxtG0BJ797+HSog+FPjiWKjNxnPgpslUIQha8CoxqFZln2ZeBRv00tzGIhUAQKsw==",
"license": "MIT"
},
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
@ -3417,6 +3787,28 @@
"node": ">=0.10.0"
}
},
"node_modules/fast-check": {
"version": "3.19.0",
"resolved": "https://registry.npmjs.org/fast-check/-/fast-check-3.19.0.tgz",
"integrity": "sha512-CO2JX/8/PT9bDGO1iXa5h5ey1skaKI1dvecERyhH4pp3PGjwd3KIjMAXEg79Ps9nclsdt4oPbfqiAnLU0EwrAQ==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/dubzzz"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fast-check"
}
],
"license": "MIT",
"dependencies": {
"pure-rand": "^6.1.0"
},
"engines": {
"node": ">=8.0.0"
}
},
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@ -3481,6 +3873,18 @@
"node": "^10.12.0 || >=12.0.0"
}
},
"node_modules/file-selector": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz",
"integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==",
"license": "MIT",
"dependencies": {
"tslib": "^2.4.0"
},
"engines": {
"node": ">= 12"
}
},
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@ -4440,6 +4844,15 @@
"node": ">=4.0"
}
},
"node_modules/kareem": {
"version": "2.6.3",
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.6.3.tgz",
"integrity": "sha512-C3iHfuGUXK2u8/ipq9LfjFfXFxAZMQJJq7vLS45r3D9Y2xQ/m4S8zaR4zMLFWh9AsNPXmcFfUDhTEO8UIC/V6Q==",
"license": "Apache-2.0",
"engines": {
"node": ">=12.0.0"
}
},
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@ -4541,6 +4954,12 @@
"react": "^16.5.1 || ^17.0.0 || ^18.0.0"
}
},
"node_modules/memory-pager": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/memory-pager/-/memory-pager-1.5.0.tgz",
"integrity": "sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==",
"license": "MIT"
},
"node_modules/merge2": {
"version": "1.4.1",
"resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
@ -4599,11 +5018,115 @@
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/mongodb": {
"version": "6.6.2",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.6.2.tgz",
"integrity": "sha512-ZF9Ugo2JCG/GfR7DEb4ypfyJJyiKbg5qBYKRintebj8+DNS33CyGMkWbrS9lara+u+h+yEOGSRiLhFO/g1s1aw==",
"license": "Apache-2.0",
"dependencies": {
"@mongodb-js/saslprep": "^1.1.5",
"bson": "^6.7.0",
"mongodb-connection-string-url": "^3.0.0"
},
"engines": {
"node": ">=16.20.1"
},
"peerDependencies": {
"@aws-sdk/credential-providers": "^3.188.0",
"@mongodb-js/zstd": "^1.1.0",
"gcp-metadata": "^5.2.0",
"kerberos": "^2.0.1",
"mongodb-client-encryption": ">=6.0.0 <7",
"snappy": "^7.2.2",
"socks": "^2.7.1"
},
"peerDependenciesMeta": {
"@aws-sdk/credential-providers": {
"optional": true
},
"@mongodb-js/zstd": {
"optional": true
},
"gcp-metadata": {
"optional": true
},
"kerberos": {
"optional": true
},
"mongodb-client-encryption": {
"optional": true
},
"snappy": {
"optional": true
},
"socks": {
"optional": true
}
}
},
"node_modules/mongodb-connection-string-url": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/mongodb-connection-string-url/-/mongodb-connection-string-url-3.0.1.tgz",
"integrity": "sha512-XqMGwRX0Lgn05TDB4PyG2h2kKO/FfWJyCzYQbIhXUxz7ETt0I/FqHjUeqj37irJ+Dl1ZtU82uYyj14u2XsZKfg==",
"license": "Apache-2.0",
"dependencies": {
"@types/whatwg-url": "^11.0.2",
"whatwg-url": "^13.0.0"
}
},
"node_modules/mongoose": {
"version": "8.4.3",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-8.4.3.tgz",
"integrity": "sha512-GxPVLD+I/dxVkgcts2r2QmJJvS62/++btVj3RFt8YnHt+DSOp1Qjj62YEvgZaElwIOTcc4KGJM95X5LlrU1qQg==",
"license": "MIT",
"dependencies": {
"bson": "^6.7.0",
"kareem": "2.6.3",
"mongodb": "6.6.2",
"mpath": "0.9.0",
"mquery": "5.0.0",
"ms": "2.1.3",
"sift": "17.1.3"
},
"engines": {
"node": ">=16.20.1"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mongoose"
}
},
"node_modules/mongoose/node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
"license": "MIT"
},
"node_modules/mpath": {
"version": "0.9.0",
"resolved": "https://registry.npmjs.org/mpath/-/mpath-0.9.0.tgz",
"integrity": "sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==",
"license": "MIT",
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/mquery": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz",
"integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==",
"license": "MIT",
"dependencies": {
"debug": "4.x"
},
"engines": {
"node": ">=14.0.0"
}
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"dev": true
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/mz": {
"version": "2.7.0",
@ -5191,11 +5714,26 @@
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
"integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
"dev": true,
"engines": {
"node": ">=6"
}
},
"node_modules/pure-rand": {
"version": "6.1.0",
"resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz",
"integrity": "sha512-bVWawvoZoBYpp6yIoQtQXHZjmz35RSVHnUOTefl8Vcjr8snTPY1wnpSPMWekcFwbxI6gtmT7rSYPFvz71ldiOA==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/dubzzz"
},
{
"type": "opencollective",
"url": "https://opencollective.com/fast-check"
}
],
"license": "MIT"
},
"node_modules/queue-microtask": {
"version": "1.2.3",
"resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
@ -5252,6 +5790,22 @@
"react": ">=0.12.0"
}
},
"node_modules/react-hook-form": {
"version": "7.52.0",
"resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.52.0.tgz",
"integrity": "sha512-mJX506Xc6mirzLsmXUJyqlAI3Kj9Ph2RhplYhUVffeOQSnubK2uVqBFOBJmvKikvbFV91pxVXmDiR+QMF19x6A==",
"license": "MIT",
"engines": {
"node": ">=12.22.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/react-hook-form"
},
"peerDependencies": {
"react": "^16.8.0 || ^17 || ^18 || ^19"
}
},
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
@ -5613,6 +6167,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/sift": {
"version": "17.1.3",
"resolved": "https://registry.npmjs.org/sift/-/sift-17.1.3.tgz",
"integrity": "sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==",
"license": "MIT"
},
"node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
@ -5641,6 +6201,21 @@
"node": ">=0.10.0"
}
},
"node_modules/sparse-bitfield": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/sparse-bitfield/-/sparse-bitfield-3.0.3.tgz",
"integrity": "sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==",
"license": "MIT",
"dependencies": {
"memory-pager": "^1.0.2"
}
},
"node_modules/std-env": {
"version": "3.7.0",
"resolved": "https://registry.npmjs.org/std-env/-/std-env-3.7.0.tgz",
"integrity": "sha512-JPbdCEQLj1w5GilpiHAx3qJvFndqybBysA3qUOnznweH4QbNYUsW/ea8QzSrnh0vNsezMMw5bcVool8lM0gwzg==",
"license": "MIT"
},
"node_modules/streamsearch": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz",
@ -5996,6 +6571,18 @@
"node": ">=8.0"
}
},
"node_modules/tr46": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-4.1.1.tgz",
"integrity": "sha512-2lv/66T7e5yNyhAAC4NaKe5nVavzuGJQVVtRYLyQ2OI8tsJ61PMLlelehb0wi2Hx6+hT/OJUWZcw8MjlSRnxvw==",
"license": "MIT",
"dependencies": {
"punycode": "^2.3.0"
},
"engines": {
"node": ">=14"
}
},
"node_modules/ts-api-utils": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.3.0.tgz",
@ -6161,6 +6748,48 @@
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
},
"node_modules/uploadthing": {
"version": "6.12.0",
"resolved": "https://registry.npmjs.org/uploadthing/-/uploadthing-6.12.0.tgz",
"integrity": "sha512-uoWG1riH6z2IHCcbMo3xnGe6p/+sx2PPOOLOsk5DeqGv5HtlY7ISauFFRXW8H+jdhevZm1n/j4Je/Z+bbIziSg==",
"license": "MIT",
"dependencies": {
"@effect/schema": "^0.66.12",
"@uploadthing/mime-types": "0.2.10",
"@uploadthing/shared": "6.7.5",
"consola": "^3.2.3",
"effect": "^3.1.0",
"fast-check": "^3.18.0",
"std-env": "^3.7.0"
},
"engines": {
"node": ">=18.13.0"
},
"peerDependencies": {
"express": "*",
"fastify": "*",
"h3": "*",
"next": "*",
"tailwindcss": "*"
},
"peerDependenciesMeta": {
"express": {
"optional": true
},
"fastify": {
"optional": true
},
"h3": {
"optional": true
},
"next": {
"optional": true
},
"tailwindcss": {
"optional": true
}
}
},
"node_modules/uri-js": {
"version": "4.4.1",
"resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
@ -6216,6 +6845,28 @@
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"node_modules/webidl-conversions": {
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
"integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
"license": "BSD-2-Clause",
"engines": {
"node": ">=12"
}
},
"node_modules/whatwg-url": {
"version": "13.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-13.0.0.tgz",
"integrity": "sha512-9WWbymnqj57+XEuqADHrCJ2eSXzn8WXIW/YSGaZtb2WKAInQ6CHfaUUcTyyver0p8BDg5StLQq8h1vtZuwmOig==",
"license": "MIT",
"dependencies": {
"tr46": "^4.1.1",
"webidl-conversions": "^7.0.0"
},
"engines": {
"node": ">=16"
}
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@ -6433,6 +7084,15 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/zod": {
"version": "3.23.8",
"resolved": "https://registry.npmjs.org/zod/-/zod-3.23.8.tgz",
"integrity": "sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g==",
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
}
}
}

View file

@ -12,26 +12,34 @@
"@fortawesome/fontawesome-svg-core": "^6.5.2",
"@fortawesome/free-regular-svg-icons": "^6.5.2",
"@fortawesome/react-fontawesome": "^0.2.2",
"@hookform/resolvers": "^3.6.0",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-label": "^2.1.0",
"@radix-ui/react-navigation-menu": "^1.1.4",
"@radix-ui/react-slot": "^1.0.2",
"@radix-ui/react-slot": "^1.1.0",
"@radix-ui/themes": "^3.0.5",
"@types/cookie": "^0.6.0",
"@uploadthing/react": "^6.6.0",
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.1",
"dotenv": "^16.4.5",
"cookie": "^0.6.0",
"framer-motion": "^11.2.10",
"lucide-react": "^0.394.0",
"mini-svg-data-uri": "^1.4.4",
"mongoose": "^8.4.3",
"next": "14.2.3",
"next-themes": "^0.3.0",
"nookies": "^2.5.2",
"react": "^18",
"react-dom": "^18",
"react-fontawesome": "^1.7.1",
"react-hook-form": "^7.52.0",
"tailwind-merge": "^2.3.0",
"tailwindcss-animate": "^1.0.7"
"tailwindcss-animate": "^1.0.7",
"uploadthing": "^6.12.0",
"zod": "^3.23.8"
},
"devDependencies": {
"@types/node": "^20",