added vulnerabilties

This commit is contained in:
Cypro Freelance 2024-07-31 00:47:07 +05:30
commit 94ff7b0ae0
159 changed files with 26455 additions and 0 deletions

12
.env.example Normal file
View file

@ -0,0 +1,12 @@
MONGODB_URI=
UPLOADTHING_SECRET=
UPLOADTHING_APP_ID=
ADMIN_USERNAME=
ADMIN_PASSWORD=
NEXTAUTH_SECRET=
EMAIL=
EMAIL_PASS=

3
.eslintrc.json Normal file
View file

@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}

36
.gitignore vendored Normal file
View file

@ -0,0 +1,36 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
.yarn/install-state.gz
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

36
README.md Normal file
View file

@ -0,0 +1,36 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
## Getting Started
First, run the development server:
```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.

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

@ -0,0 +1,13 @@
// 'use server';
// import { NextApiRequest } from 'next';
// import { NextResponse } from 'next/server';
// export async function POST(req: NextApiRequest) {
// const { username, password } = await req.body;
// if (username === process.env.USERNAME && password === process.env.PASSWORD) {
// return NextResponse.json({ success: true });
// } else {
// return NextResponse.json({ success: false });
// }
// }

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,57 @@
"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";
import Logo from "@/components/shared/Logo";
const MobileNav = () => {
const pathname = usePathname();
return (
<header className="header">
<Link href="/" className="flex items-center gap-2 md:py-2">
<Logo width={120} height={40} />
</Link>
<nav className="flex gap-2">
<Sheet>
<SheetTrigger>
<Menu className="w-5 h-5" />
</SheetTrigger>
<SheetContent className="sheet-content sm:w-64">
<>
<Logo width={155} height={53} />
<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 { usePathname } from "next/navigation";
import { AdminLinks } from "@/constants";
import Logo from "@/components/shared/Logo";
const Sidebar = () => {
const pathname = usePathname();
return (
<>
<aside className="sidebar">
<div className="flex size-full flex-col gap-4">
<Link href="/" className="sidebar-logo">
<Logo width={155} height={53} />
</Link>
<nav className="sidebar-nav">
<ul className="sidebar-nav_elements">
{AdminLinks.slice(0, 5).map((link) => {
const isActive = link.url === pathname;
return (
<li
key={link.url}
className={`sidebar-nav_element group ${
isActive ? "bg-white/5" : "text-muted-foreground"
}`}
>
<Link className="sidebar-link" href={link.url}>
<link.icon />
{link.name}
</Link>
</li>
);
})}
</ul>
<ul className="sidebar-nav_elements">
{AdminLinks.slice(5).map((link) => {
const isActive = link.url === pathname;
return (
<li
key={link.url}
className={`sidebar-nav_element group ${
isActive ? "bg-purple-gradient" : "text-muted-foreground"
}`}
>
<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,9 @@
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Admin // Changelogs",
};
export default function logPages({ children }: { children: React.ReactNode }) {
return <>{children}</>;
}

View file

@ -0,0 +1,232 @@
"use client";
import React, { useEffect, useState } from "react";
import { useForm, SubmitHandler, useFieldArray } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import {
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Input } from "@/components/ui/input";
import { logsSchema } from "@/lib/validations/validation";
import { z } from "zod";
import { useToast } from "@/components/ui/use-toast";
interface LogEntry {
_id: string;
version: string;
date: string;
bullets: { point: string }[];
}
type LogsFormValues = z.infer<typeof logsSchema>;
const AdminLogPage = () => {
const [logs, setLogs] = useState<LogEntry[]>([]);
const [error, setError] = useState("");
const { toast } = useToast();
const [loading, setLoading] = useState(false);
const form = useForm<LogsFormValues>({
resolver: zodResolver(logsSchema),
defaultValues: {
version: "",
date: "",
bullets: [{ point: "" }],
},
});
const { fields, append, remove } = useFieldArray({
control: form.control,
name: "bullets",
});
const fetchLogs = async () => {
try {
const response = await fetch("/api/logs", { method: "GET" });
if (response.ok) {
const data: LogEntry[] = await response.json();
setLogs(data);
} else {
throw new Error(`HTTP error! status: ${response.status}`);
}
} catch (error: any) {
setError(error.message || "Failed to fetch logs");
}
};
const onSubmit: SubmitHandler<LogsFormValues> = async (data) => {
setLoading(true);
const response = await fetch("/api/uploadlogs", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
if (response.ok) {
form.reset();
fetchLogs();
setLoading(false);
toast({ description: "Logs successfully added" });
} else {
setLoading(false);
toast({ description: "Upload Failed", variant: "destructive" });
}
};
const deleteLog = async (id: string) => {
try {
const response = await fetch(`/api/delete/logs/${id}`, {
method: "DELETE",
});
if (response.ok) {
fetchLogs();
} else {
throw new Error(`HTTP error! status: ${response.status}`);
}
} catch (error: any) {
setError(error.message || "Failed to delete log");
}
};
useEffect(() => {
fetchLogs();
const interval = setInterval(() => {
fetchLogs();
}, 10000);
return () => clearInterval(interval);
}, []);
return (
<section id="logs-page" className="wrapper container">
<h1 className="text-3xl font-bold py-6">Server Logs Form</h1>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="version"
render={({ field }) => (
<FormItem>
<FormLabel>Version Name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="date"
render={({ field }) => (
<FormItem>
<FormLabel>Date</FormLabel>
<FormControl>
<Input {...field} placeholder="Released on 24 Nov 2024" />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{fields.map((field, index) => (
<FormField
key={field.id}
control={form.control}
name={`bullets.${index}.point`}
render={({ field }) => (
<FormItem>
<FormLabel>Key Point {index + 1}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
<Button
type="button"
className="mt-2"
variant={"secondary"}
onClick={() => remove(index)}
>
Remove
</Button>
</FormItem>
)}
/>
))}
<Button
type="button"
className="mb-4"
size={"icon"}
variant={"outline"}
onClick={() => append({ point: "" })}
>
+
</Button>
<Button
type="submit"
className="w-full text-lg rounded-full"
disabled={loading}
size={"lg"}
>
Submit
</Button>
</form>
</Form>
{/* Section to list and delete logs */}
<section id="logs-list" className="py-16 md:py-24">
<h2 className="text-3xl md:text-4xl font-bold">Existing Logs</h2>
{error && <p className="text-red-500">{error}</p>}
<Table className="w-full mt-4 border-muted">
<TableHeader>
<TableRow>
<TableHead className="border-b px-4 py-2">Version</TableHead>
<TableHead className="border-b px-4 py-2">Date</TableHead>
<TableHead className="border-b px-4 py-2">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{logs
.slice()
.reverse()
.map((log) => (
<TableRow key={log._id}>
<TableCell className="border-b px-4 py-2">
{log.version}
</TableCell>
<TableCell className="border-b px-4 py-2">
{log.date}
</TableCell>
<TableCell className="border-b px-4 py-2">
<Button
variant={"destructive"}
onClick={() => deleteLog(log._id)}
>
Delete
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</section>
</section>
);
};
export default AdminLogPage;

View file

@ -0,0 +1,9 @@
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Admin // Downloads",
};
export default function logPages({ children }: { children: React.ReactNode }) {
return <>{children}</>;
}

View file

@ -0,0 +1,251 @@
"use client";
import React, { useEffect, useState } 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 } from "@/lib/uploadthing";
import { downloadSchema } from "@/lib/validations/validation";
import { useToast } from "@/components/ui/use-toast";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
interface DownloadEntry {
_id: string;
fileName: string;
version: string;
downloadLink: string;
fileSize: string;
}
const DownloadsPage = () => {
const { toast } = useToast();
const [downloads, setDownloads] = useState<DownloadEntry[]>([]);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const form = useForm<z.infer<typeof downloadSchema>>({
resolver: zodResolver(downloadSchema),
defaultValues: {
fileName: "",
version: "",
downloadLink: "",
fileSize: "",
},
});
const fetchDownloads = async () => {
try {
const response = await fetch("/api/downloads", {
method: "GET",
});
if (response.ok) {
const data: DownloadEntry[] = await response.json();
setDownloads(data);
} else {
throw new Error(`HTTP error! status: ${response.status}`);
}
} catch (error: any) {
setError(error.message || "Failed to fetch downloads");
}
};
useEffect(() => {
fetchDownloads();
const interval = setInterval(() => {
fetchDownloads();
}, 10000);
return () => clearInterval(interval);
}, []);
const onSubmit: SubmitHandler<z.infer<typeof downloadSchema>> = async (
data
) => {
setLoading(true);
const response = await fetch("/api/upload", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
if (response.ok) {
form.reset();
fetchDownloads();
setLoading(false);
toast({ description: "Download Successfully Updated" });
} else {
console.error("Upload failed");
setLoading(false);
toast({ description: "Uploading Failed", variant: "destructive" });
}
};
const deleteDownload = async (id: string) => {
try {
const response = await fetch(`/api/delete/downloads/${id}`, {
method: "DELETE",
});
if (response.ok) {
fetchDownloads();
} else {
throw new Error(`HTTP error! status: ${response.status}`);
}
} catch (error: any) {
setError(error.message || "Failed to delete download");
}
};
return (
<section id="downloads-page" className="wrapper container">
<h1 className="text-3xl font-bold py-6">Downloads Form</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"}
disabled={loading}
>
Submit
</Button>
</form>
</Form>
{/* Section to list and delete downloads */}
<section id="downloads-list" className="py-16 md:py-24">
<h2 className="text-3xl md:text-4xl font-bold">Existing Downloads</h2>
{error && <p className="text-red-500">{error}</p>}
<Table className="w-full mt-4 border-muted">
<TableHeader>
<TableRow>
<TableHead className="border-b px-4 py-2">File Name</TableHead>
<TableHead className="border-b px-4 py-2">Version</TableHead>
<TableHead className="border-b px-4 py-2">
Download Link
</TableHead>
<TableHead className="border-b px-4 py-2">File Size</TableHead>
<TableHead className="border-b px-4 py-2">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{downloads
.slice()
.reverse()
.map((download) => (
<TableRow key={download._id}>
<TableCell className="border-b px-4 py-2">
{download.fileName}
</TableCell>
<TableCell className="border-b px-4 py-2">
{download.version}
</TableCell>
<TableCell className="border-b px-4 py-2">
<a
href={download.downloadLink}
target="_blank"
rel="noopener noreferrer"
>
{download.downloadLink}
</a>
</TableCell>
<TableCell className="border-b px-4 py-2">
{download.fileSize}
</TableCell>
<TableCell className="border-b px-4 py-2">
<Button
variant={"destructive"}
onClick={() => deleteDownload(download._id)}
>
Delete
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</section>
</section>
);
};
export default DownloadsPage;

View file

@ -0,0 +1,9 @@
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Admin // Mods",
};
export default function logPages({ children }: { children: React.ReactNode }) {
return <>{children}</>;
}

View file

@ -0,0 +1,254 @@
"use client";
import React, { useEffect, useState } 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 } from "@/lib/uploadthing";
import { modsSchema } from "@/lib/validations/validation";
import { useToast } from "@/components/ui/use-toast";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
interface ModEntry {
_id: string;
fileName: string;
version: string;
downloadLink: string;
fileSize: string;
}
const SvrjsModsAdminPage = () => {
const { toast } = useToast();
const [mods, setMods] = useState<ModEntry[]>([]);
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const form = useForm<z.infer<typeof modsSchema>>({
resolver: zodResolver(modsSchema),
defaultValues: {
fileName: "",
version: "",
downloadLink: "",
fileSize: "",
},
});
const fetchMods = async () => {
try {
const response = await fetch("/api/mods", {
method: "GET",
});
if (response.ok) {
const data: ModEntry[] = await response.json();
setMods(data);
} else {
throw new Error(`HTTP error! status: ${response.status}`);
}
} catch (error: any) {
setError(error.message || "Failed to fetch mods");
}
};
useEffect(() => {
fetchMods();
const interval = setInterval(() => {
fetchMods();
}, 10000);
return () => clearInterval(interval);
}, []);
const onSubmit: SubmitHandler<z.infer<typeof modsSchema>> = async (data) => {
setLoading(true);
const response = await fetch("/api/uploadmods", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});
if (response.ok) {
form.reset();
fetchMods();
setLoading(false);
toast({
description: "Successfully Uploaded Mods",
});
} else {
console.error("Upload failed");
setLoading(false);
toast({
description: "Upload failed",
variant: "destructive",
});
}
};
const deleteMod = async (id: string) => {
try {
const response = await fetch(`/api/delete/mods/${id}`, {
method: "DELETE",
});
if (response.ok) {
fetchMods();
} else {
throw new Error(`HTTP error! status: ${response.status}`);
}
} catch (error: any) {
setError(error.message || "Failed to delete mod");
}
};
return (
<section id="mods-page" className="wrapper container">
<h1 className="text-3xl font-bold py-6">Mods Form</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"}
disabled={loading}
>
Submit
</Button>
</form>
</Form>
{/* Section to list and delete mods */}
<section id="mods-list" className="py-16 md:py-24">
<h2 className="text-3xl md:text-4xl font-bold">Existing Mods</h2>
{error && <p className="text-red-500">{error}</p>}
<Table className="w-full mt-4 border-muted">
<TableHeader>
<TableRow>
<TableHead className="border-b px-4 py-2">File Name</TableHead>
<TableHead className="border-b px-4 py-2">Version</TableHead>
<TableHead className="border-b px-4 py-2">
Download Link
</TableHead>
<TableHead className="border-b px-4 py-2">File Size</TableHead>
<TableHead className="border-b px-4 py-2">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{mods
.slice()
.reverse()
.map((mod) => (
<TableRow key={mod._id}>
<TableCell className="border-b px-4 py-2">
{mod.fileName}
</TableCell>
<TableCell className="border-b px-4 py-2">
{mod.version}
</TableCell>
<TableCell className="border-b px-4 py-2">
<a
href={mod.downloadLink}
target="_blank"
rel="noopener noreferrer"
>
{mod.downloadLink}
</a>
</TableCell>
<TableCell className="border-b px-4 py-2">
{mod.fileSize}
</TableCell>
<TableCell className="border-b px-4 py-2">
<Button
variant={"destructive"}
onClick={() => deleteMod(mod._id)}
>
Delete
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</section>
</section>
);
};
export default SvrjsModsAdminPage;

View file

@ -0,0 +1,79 @@
"use client";
import React, { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
import dynamic from "next/dynamic";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { useToast } from "@/components/ui/use-toast";
const MarkdownEditor = dynamic(() => import("@uiw/react-md-editor"), {
ssr: false,
});
const EditPage = ({ params }: { params: { slug: string } }) => {
const router = useRouter();
const { slug } = params;
const { toast } = useToast();
const [title, setTitle] = useState("");
const [content, setContent] = useState("");
const [loading, setLoading] = useState(false);
useEffect(() => {
if (slug) {
fetch(`/api/mdx/pages/${slug}`)
.then((response) => response.json())
.then((data) => {
setTitle(data.title);
setContent(data.content);
})
.catch((error) => console.error("Failed to load page", error));
}
}, [slug]);
const savePage = async () => {
setLoading(true);
const response = await fetch(`/api/mdx/pages/${slug}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title, content }),
});
if (response.ok) {
setLoading(false);
toast({ description: "Page successfully updated" });
router.push(`/admin/multi-logs/`);
} else {
setLoading(false);
// TEMPERARORY ERROR
router.push(`/admin/multi-logs/`);
toast({ description: "Updated but cant return data" });
}
};
const handleEditorChange = (value?: string) => {
if (value !== undefined) {
setContent(value);
}
};
return (
<section id="edit-page" className="wrapper container">
<h1 className="text-3xl font-bold py-6">Edit Page: {slug}</h1>
<Input
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Page Title"
/>
<MarkdownEditor
value={content}
onChange={handleEditorChange}
height={560}
/>
<Button onClick={savePage} disabled={loading} className="mt-4">
Save
</Button>
</section>
);
};
export default EditPage;

View file

@ -0,0 +1,9 @@
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Admin // MultiLogs",
};
export default function logPages({ children }: { children: React.ReactNode }) {
return <>{children}</>;
}

View file

@ -0,0 +1,158 @@
"use client";
import React, { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Input } from "@/components/ui/input";
import { useRouter } from "next/navigation";
import { useToast } from "@/components/ui/use-toast";
import {
Dialog,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
} from "@/components/ui/dialog";
interface PageEntry {
title: string;
slug: string;
content: string;
}
const MultiLogs = () => {
const [pages, setPages] = useState<PageEntry[]>([]);
const { toast } = useToast();
const router = useRouter();
const [loading, setLoading] = useState(false);
const [open, setOpen] = useState(false);
const [pageTitle, setPageTitle] = useState("");
useEffect(() => {
fetch("/api/mdx/pages")
.then((response) => response.json())
.then((data) => setPages(data))
.catch((error) => console.error("Failed to load pages", error));
}, []);
const createPage = async () => {
setLoading(true);
const slug = pageTitle.toLowerCase().replace(/\s+/g, "-");
const response = await fetch("/api/mdx/pages", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ title: pageTitle, slug, content: "" }),
});
if (response.ok) {
const newPage = await response.json();
setPages([...pages, newPage]);
setPageTitle("");
setOpen(false);
router.push(`/admin/multi-logs/${slug}`);
toast({ description: "Page created successfully" });
} else {
const errorData = await response.json();
console.error("Failed to create page:", errorData);
toast({ description: `Error: ${errorData.message}` });
}
setLoading(false);
};
const deletePage = async (slug: string) => {
setLoading(true);
const response = await fetch(`/api/mdx/pages/${slug}`, {
method: "DELETE",
});
if (response.ok) {
setPages(pages.filter((page) => page.slug !== slug));
toast({ description: "Page deleted successfully" });
} else {
const errorData = await response.json();
console.error("Failed to delete page:", errorData);
toast({ description: `Error: ${errorData.message}` });
}
setLoading(false);
};
return (
<section id="logs-page" className="wrapper container">
<section id="create-page" className="py-16">
<h2 className="text-3xl md:text-4xl font-bold mb-2">Create New Page</h2>
<Button variant={"secondary"} onClick={() => setOpen(true)}>
Create New Page
</Button>
<Dialog open={open} onOpenChange={setOpen}>
<DialogContent>
<DialogHeader>
<DialogTitle>Enter Page Title</DialogTitle>
</DialogHeader>
<Input
value={pageTitle}
onChange={(e) => setPageTitle(e.target.value)}
placeholder="Page Title"
/>
<DialogFooter>
<Button disabled={loading} onClick={createPage}>
Continue
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
</section>
<section id="pages-list" className="pb-16">
<h2 className="text-3xl md:text-4xl font-bold">Existing Pages</h2>
<p className="mb-4">Total Pages: {pages.length}</p>
<Table className="w-full mt-4 border-muted">
<TableHeader>
<TableRow>
<TableHead className="border-b px-4 py-2">Slug</TableHead>
<TableHead className="border-b px-4 py-2">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{pages.map((page) => (
<TableRow key={page.slug}>
<TableCell className="border-b px-4 py-2">
<a
href={`/changelogs/${page.slug}`}
className="text-blue-500 underline"
>
{page.slug}
</a>
</TableCell>
<TableCell className="border-b px-4 py-2">
<Button
variant={"outline"}
onClick={() =>
router.push(`/admin/multi-logs/${page.slug}`)
}
>
Edit
</Button>
<Button
variant={"destructive"}
onClick={() => deletePage(page.slug)}
className="ml-2"
disabled={loading}
>
Delete
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</section>
</section>
);
};
export default MultiLogs;

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/mods" />
<Card title="Logs" url="/admin/changelogs" />
<Card title="MultiLogs" url="/admin/multi-logs" />
</div>
</section>
</>
);
};
export default AdminPage;

View file

@ -0,0 +1,9 @@
import type { Metadata } from "next";
export const metadata: Metadata = {
title: "Admin // Vulnerabilities",
};
export default function logPages({ children }: { children: React.ReactNode }) {
return <>{children}</>;
}

View file

@ -0,0 +1,217 @@
"use client";
import React, { useEffect, useState } from "react";
import { useForm, SubmitHandler, useFieldArray } from "react-hook-form";
import { zodResolver } from "@hookform/resolvers/zod";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import {
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Input } from "@/components/ui/input";
import { logsSchema } from "@/lib/validations/validation";
import { z } from "zod";
import { useToast } from "@/components/ui/use-toast";
interface LogEntry {
_id: string;
version: string;
date: string;
bullets: { point: string }[];
}
type LogsFormValues = z.infer<typeof logsSchema>;
const AdminLogPage = () => {
const [logs, setLogs] = useState<LogEntry[]>([]);
const [error, setError] = useState("");
const { toast } = useToast();
const [loading, setLoading] = useState(false);
const form = useForm<LogsFormValues>({
resolver: zodResolver(logsSchema),
defaultValues: {
version: "",
date: "",
bullets: [{ point: "" }],
},
});
const { fields, append, remove } = useFieldArray({
control: form.control,
name: "bullets",
});
const fetchLogs = async () => {
try {
const response = await fetch("/api/vulnerabilties", { method: "GET" });
if (response.ok) {
const data: LogEntry[] = await response.json();
setLogs(data);
} else {
throw new Error(`HTTP error! status: ${response.status}`);
}
} catch (error: any) {
setError(error.message || "Failed to fetch logs");
}
};
const onSubmit: SubmitHandler<LogsFormValues> = async (data) => {
setLoading(true);
const response = await fetch("/api/uploadvulnerabilities", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
});
if (response.ok) {
form.reset();
fetchLogs();
setLoading(false);
toast({ description: "Logs successfully added" });
} else {
setLoading(false);
toast({ description: "Upload Failed", variant: "destructive" });
}
};
const deleteLog = async (id: string) => {
try {
const response = await fetch(`/api/delete/logs/${id}`, {
method: "DELETE",
});
if (response.ok) {
fetchLogs();
} else {
throw new Error(`HTTP error! status: ${response.status}`);
}
} catch (error: any) {
setError(error.message || "Failed to delete log");
}
};
useEffect(() => {
fetchLogs();
const interval = setInterval(() => {
fetchLogs();
}, 10000);
return () => clearInterval(interval);
}, []);
return (
<section id="logs-page" className="wrapper container">
<h1 className="text-3xl font-bold py-6">Server Vulnerabilties Form</h1>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
<FormField
control={form.control}
name="version"
render={({ field }) => (
<FormItem>
<FormLabel>Version Name</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{fields.map((field, index) => (
<FormField
key={field.id}
control={form.control}
name={`bullets.${index}.point`}
render={({ field }) => (
<FormItem>
<FormLabel>Key Point {index + 1}</FormLabel>
<FormControl>
<Input {...field} />
</FormControl>
<FormMessage />
<Button
type="button"
className="mt-2"
variant={"secondary"}
onClick={() => remove(index)}
>
Remove
</Button>
</FormItem>
)}
/>
))}
<Button
type="button"
className="mb-4"
size={"icon"}
variant={"outline"}
onClick={() => append({ point: "" })}
>
+
</Button>
<Button
type="submit"
className="w-full text-lg rounded-full"
disabled={loading}
size={"lg"}
>
Submit
</Button>
</form>
</Form>
{/* Section to list and delete logs */}
<section id="logs-list" className="py-16 md:py-24">
<h2 className="text-3xl md:text-4xl font-bold">
Existing Vulnerabilties
</h2>
{error && <p className="text-red-500">{error}</p>}
<Table className="w-full mt-4 border-muted">
<TableHeader>
<TableRow>
<TableHead className="border-b px-4 py-2">Version</TableHead>
<TableHead className="border-b px-4 py-2">Actions</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{logs
.slice()
.reverse()
.map((log) => (
<TableRow key={log._id}>
<TableCell className="border-b px-4 py-2">
{log.version}
</TableCell>
<TableCell className="border-b px-4 py-2">
<Button
variant={"destructive"}
onClick={() => deleteLog(log._id)}
>
Delete
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</section>
</section>
);
};
export default AdminLogPage;

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="root-container lg:px-24">{children}</div>
</main>
);
}

11
app/(root)/blog/page.tsx Normal file
View file

@ -0,0 +1,11 @@
import React from "react";
import { Metadata } from "next";
export const metadata: Metadata = {
title: "Blog - SVRJS",
};
const BlogPage = () => {
return <div>BlogPage</div>;
};
export default BlogPage;

View file

@ -0,0 +1,87 @@
"use client";
import { Skeleton } from "@/components/ui/skeleton";
import React, { useEffect, useState } from "react";
import ReactMarkdown from "react-markdown";
import Head from "next/head";
const Page = ({ params }: { params: { slug: string } }) => {
const { slug } = params;
const [page, setPage] = useState<{ title: string; content: string } | null>(
null
);
const [loading, setLoading] = useState(true);
const [notFound, setNotFound] = useState(false);
useEffect(() => {
const fetchPage = async () => {
try {
const response = await fetch(`/api/mdx/pages/${slug}`);
if (response.ok) {
const data = await response.json();
setPage(data);
return (document.title = `${data.title} | SVRJS`);
} else {
if (response.status === 404) {
setNotFound(true);
return (document.title = "404 Not Found");
}
}
} catch (error) {
console.error("Failed to load page", error);
setNotFound(true);
} finally {
setLoading(false);
}
};
fetchPage();
}, [slug]);
if (loading) {
return (
<section className="wrapper container py-24 md:py-28 gap-4 flex flex-col">
<div className="mb-3">
<Skeleton className="w-[400px] h-[50px] rounded-md" />
</div>
<div className="flex flex-col gap-4">
<Skeleton className="w-[300px] h-[30px] rounded-md" />
<Skeleton className="w-[200px] h-[20px] rounded-md" />
<Skeleton className="w-[200px] h-[20px] rounded-md" />
<Skeleton className="w-[200px] h-[20px] rounded-md" />
</div>
</section>
);
}
if (notFound) {
return (
<section id="404error" className="flex-center flex-col wrapper container">
<h1 className="text-3xl md:text-5xl text-center">
<span className="text-red-500">404</span> Page not Found
</h1>
<p className="text-lg mt-3 text-muted-foreground">
Please return back to Home
</p>
</section>
);
}
if (!page) {
return null;
}
return (
<>
<section className="wrapper container py-24 md:py-28 gap-2 flex flex-col">
<h1 className="text-3xl md:text-5xl pb-1 md:pb-2 font-bold text-black dark:bg-clip-text dark:text-transparent dark:bg-gradient-to-b dark:from-white dark:to-neutral-400">
{page.title}
</h1>
<ReactMarkdown className="prose max-w-full md:prose-lg dark:prose-invert">
{page.content}
</ReactMarkdown>
</section>
</>
);
};
export default Page;

View file

@ -0,0 +1,86 @@
"use client";
import { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import { Download } from "lucide-react";
import Link from "next/link";
import ReactMarkdown from "react-markdown";
import { CHANGE_LOGS } from "@/constants/guidelines";
interface Bullet {
point: string;
}
interface LOGS {
_id: string;
date: string;
version: string;
bullets?: Bullet[]; // Make bullets optional
}
const LogsPage: React.FC = () => {
const [downloads, setDownloads] = useState<LOGS[]>([]);
const [error, setError] = useState("");
const fetchDownloads = async () => {
try {
const response = await fetch("/api/logs", {
method: "GET",
});
if (response.ok) {
const data: LOGS[] = await response.json();
setDownloads(data);
} else {
throw new Error(`HTTP error! status: ${response.status}`);
}
} catch (error: any) {
setError(error.message || "Failed to fetch downloads");
}
};
useEffect(() => {
fetchDownloads();
const interval = setInterval(() => {
fetchDownloads();
}, 10000);
return () => clearInterval(interval);
}, []);
const reversedDownloads = [...downloads].reverse();
return (
<section
id="logs"
className="wrapper container py-24 md:py-28 gap-2 flex flex-col"
>
<h1 className="text-3xl md:text-5xl pb-1 md:pb-2 font-bold text-black dark:bg-clip-text dark:text-transparent dark:bg-gradient-to-b dark:from-white dark:to-neutral-400">
Server LOGS
</h1>
<p className="md:text-lg text-muted-foreground text-start mb-6">
Get all the latest version of SVRJS download and compiled Files here!
</p>
{error && <p className="text-red-500">{error}</p>}
{reversedDownloads.map((download) => (
<div
key={download._id}
className="flex-start prose max-w-full md:prose-lg dark:prose-invert flex-col mb-4"
>
<h2 className="font-bold text-3xl">{download.version}</h2>
<span className="font-medium italic">{download.date}</span>
<ul className="list-disc pl-5">
{(download.bullets ?? []).map((bullet, index) => (
<li key={index}>{bullet.point}</li>
))}
</ul>
</div>
))}
<div className="prose max-w-full md:prose-lg dark:prose-invert">
<ReactMarkdown>{CHANGE_LOGS}</ReactMarkdown>
</div>
</section>
);
};
export default LogsPage;

179
app/(root)/contact/page.tsx Normal file
View file

@ -0,0 +1,179 @@
"use client";
import Iconss from "@/components/ui/icons";
import { Mail, Send, WebhookIcon, Bug, Shield } from "lucide-react";
import { zodResolver } from "@hookform/resolvers/zod";
import { useForm } from "react-hook-form";
import { z } from "zod";
import { contactFormSchema } from "@/lib/validations/validation";
import { Button } from "@/components/ui/button";
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form";
import { Input } from "@/components/ui/input";
import { Textarea } from "@/components/ui/textarea";
import { useToast } from "@/components/ui/use-toast";
import { useState } from "react";
import { Separator } from "@/components/ui/separator";
import { emails } from "@/constants";
const ContactUs = () => {
const { toast } = useToast();
const [loading, setLoading] = useState(false);
const form = useForm<z.infer<typeof contactFormSchema>>({
resolver: zodResolver(contactFormSchema),
defaultValues: {
name: "",
email: "",
message: "",
},
});
async function onSubmit(values: z.infer<typeof contactFormSchema>) {
setLoading(true);
try {
const res = await fetch("/api/contact", {
method: "POST",
body: JSON.stringify(values),
headers: {
"Content-Type": "application/json",
Accept: "application/json",
},
});
if (res.ok) {
form.reset();
toast({
description: "Your message has been sent.",
});
setLoading(false);
} else {
toast({
title: "Uh oh! Something went wrong.",
variant: "destructive",
});
setLoading(false);
}
} catch (error) {
console.error(error);
toast({
title: "Uh oh! Something went wrong.",
variant: "destructive",
});
setLoading(false);
}
}
return (
<>
<div className="flex items-center justify-center py-12 md:py-16 w-full transition-all duration-300">
<h1 className="text-4xl md:text-6xl tracking-tight font-bold uppercase text-center text-gray-900 dark:text-white">
Contact Us
</h1>
</div>
<section id="contact" className="w-full">
<div className="flex-center flex-col md:flex-row justify-between mx-auto p-6 max-w-5xl">
{/* Left contact page */}
<Form {...form}>
<form
onSubmit={form.handleSubmit(onSubmit)}
className="space-y-4 pb-8 mb-8 max-w-lg w-full bg-accent border p-6 rounded-lg shadow-md"
>
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormControl>
<Input placeholder="Your Name" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="email"
render={({ field }) => (
<FormItem>
<FormLabel>Email</FormLabel>
<FormControl>
<Input type="email" placeholder="Your Email" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<FormField
control={form.control}
name="message"
render={({ field }) => (
<FormItem>
<FormLabel>Message</FormLabel>
<FormControl>
<Textarea
className="h-44"
placeholder="Your Message"
{...field}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
<Button
type="submit"
variant={"default"}
className="w-full mt-2"
disabled={loading}
>
<div className="flex items-center justify-center">
<span className="tracking-tight font-semibold">SEND</span>
<Send className="ml-2 w-5 h-5" />
</div>
</Button>
</form>
</Form>
{/* Right contact page */}
<div className="max-w-lg mt-8 md:mt-0 md:ml-8 p-12 border rounded-lg">
<ul className="space-y-4 mb-6">
{emails.map((email, index) => (
<li
key={index}
className="text-gray-600 dark:text-gray-300 flex items-center"
>
<email.icon className="mr-2" size={24} />
<span>
<a
href={email.url}
title={`Send an email to ${email.email}`}
className="text-muted-foreground hover:text-accent-foreground transition duration-200"
>
{email.email}
</a>
</span>
</li>
))}
</ul>
<Separator />
<ul className="flex justify-center space-x-3 my-6">
<Iconss />
</ul>
<Separator />
<div className="text-center text-gray-500 mt-2 text-sm font-light"></div>
</div>
</div>
</section>
</>
);
};
export default ContactUs;

View file

@ -0,0 +1,29 @@
import ReactMarkdown from "react-markdown";
import { contribute } from "@/constants/guidelines";
import { Metadata } from "next";
export const metadata: Metadata = {
title: "Contribute - SVRJS",
};
const Contribute = () => {
return (
<section
id="contribute"
className="wrapper container py-24 md:py-28 gap-2 flex flex-col"
>
<h1 className="text-3xl md:text-5xl pb-1 md:pb-2 font-bold text-black dark:bg-clip-text dark:text-transparent dark:bg-gradient-to-b dark:from-white dark:to-neutral-400">
Contributing to SVR.JS
</h1>
<p className="md:text-lg text-muted-foreground text-start mb-6">
We welcome contributions from the community! Here&apos;s how you can
help!
</p>
<div className="prose max-w-full md:prose-lg dark:prose-invert">
<ReactMarkdown>{contribute}</ReactMarkdown>
</div>
</section>
);
};
export default Contribute;

View file

@ -0,0 +1,13 @@
import { Metadata } from "next";
export const metadata: Metadata = {
title: "Downloads - SVRJS",
};
export default function DownloadLayout({
children,
}: {
children: React.ReactNode;
}) {
return <main>{children}</main>;
}

View file

@ -0,0 +1,106 @@
"use client";
import { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import {
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Download } from "lucide-react";
import Link from "next/link";
import { Skeleton } from "@/components/ui/skeleton";
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);
}, []);
return (
<section
id="download"
className="wrapper container py-24 md:py-28 gap-2 flex flex-col"
>
<h1 className="text-3xl md:text-5xl pb-1 md:pb-2 font-bold text-black dark:bg-clip-text dark:text-transparent dark:bg-gradient-to-b dark:from-white dark:to-neutral-400">
Downloads
</h1>
<p className="md:text-lg text-muted-foreground text-start mb-6">
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>
<TableRow>
<TableHead className="w-[150px]">Date</TableHead>
<TableHead>File Name</TableHead>
<TableHead>Version</TableHead>
<TableHead>File Size</TableHead>
<TableHead className="text-right">Download Link</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{downloads
.slice(0, 10)
.reverse()
.map((download) => (
<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="">
<Download className="w-4 h-4 mr-2" />
Download
</Button>
</Link>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</section>
);
};
export default DownloadPage;

13
app/(root)/forum/page.tsx Normal file
View file

@ -0,0 +1,13 @@
import React from "react";
import { Metadata } from "next";
export const metadata: Metadata = {
title: "Forum - SVRJS",
};
const Forum = () => {
return <div>Forum</div>;
};
export default Forum;

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

@ -0,0 +1,47 @@
import Footer from "@/components/shared/Footer";
import Navbar from "@/components/shared/Navbar";
import { Metadata } from "next";
// baseURL [ENV]
export const metadata: Metadata = {
title: "SVRJS - A Web Server running on Node.js",
description:
"Experience unparalleled flexibility with SVR.JS - the ultimate web server for Node.js. Host web pages, run server-side JavaScript, utilize mods for extended functionality, and more. Integrated log viewer and user management tools included. Also supports Bun (experimental).",
openGraph: {
title: "SVRJS - A Web Server running on Node.js",
description:
"Experience unparalleled flexibility with SVR.JS - the ultimate web server for Node.js. Host web pages, run server-side JavaScript, utilize mods for extended functionality, and more. Integrated log viewer and user management tools included. Also supports Bun (experimental).",
url: "https://svrjs.org",
type: "website",
images: [
{
url: "https://svrjs.vercel.app/metadata/svrjs-cover.png",
width: 800,
height: 600,
alt: "SVRJS - A Web Server running on Node.js",
},
],
},
twitter: {
card: "summary_large_image",
site: "@SVR_JS",
title: "SVRJS - A Web Server running on Node.js",
description:
"Experience unparalleled flexibility with SVR.JS - the ultimate web server for Node.js. Host web pages, run server-side JavaScript, utilize mods for extended functionality, and more. Integrated log viewer and user management tools included. Also supports Bun (experimental).",
images: ["https://svrjs.vercel.app/metadata/svrjs-cover.png"],
creator: "@SVR_JS",
},
};
export default function PageLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<div className="flex flex-col min-h-screen">
<Navbar />
<div className="flex-grow flex-1 overflow-x-hidden">{children}</div>
<Footer />
</div>
);
}

View file

@ -0,0 +1,11 @@
import { Metadata } from "next";
export const metadata: Metadata = {
title: "MOD - SVRJS",
};
const ModLayout = ({ children }: { children: React.ReactNode }) => {
return <main>{children}</main>;
};
export default ModLayout;

106
app/(root)/mods/page.tsx Normal file
View file

@ -0,0 +1,106 @@
"use client";
import { useEffect, useState } from "react";
import { Button } from "@/components/ui/button";
import {
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import { Download } from "lucide-react";
import Link from "next/link";
import { Skeleton } from "@/components/ui/skeleton";
interface Mods {
_id: string;
date: string;
fileName: string;
version: string;
fileSize: string;
downloadLink: string;
}
const ModsPage: React.FC = () => {
const [downloads, setDownloads] = useState<Mods[]>([]);
const [error, setError] = useState("");
const fetchDownloads = async () => {
try {
const response = await fetch("/api/mods", {
method: "GET",
});
if (response.ok) {
const data: Mods[] = 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);
}, []);
return (
<section
id="mods"
className="wrapper container py-24 md:py-28 gap-2 flex flex-col"
>
<h1 className="text-3xl md:text-5xl pb-1 md:pb-2 font-bold text-black dark:bg-clip-text dark:text-transparent dark:bg-gradient-to-b dark:from-white dark:to-neutral-400">
SvrJS Mods
</h1>
<p className="md:text-lg text-muted-foreground text-start mb-6">
Get all the latest version of SVRJS Mods and compiled Files here!{" "}
</p>
{error && <p className="text-red-500">{error}</p>}
<Table>
<TableCaption>A list of all available downloads.</TableCaption>
<TableHeader>
<TableRow>
<TableHead className="w-[150px]">Date</TableHead>
<TableHead>File Name</TableHead>
<TableHead>Version</TableHead>
<TableHead>Download Link</TableHead>
<TableHead className="text-right">File Size</TableHead>
</TableRow>
</TableHeader>
<TableBody>
{downloads
.slice(0, 10)
.reverse()
.map((download) => (
<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="">
<Download className="w-4 h-4 mr-2" />
Download
</Button>
</Link>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
</section>
);
};
export default ModsPage;

25
app/(root)/page.tsx Normal file
View file

@ -0,0 +1,25 @@
import About from "@/components/shared/About";
import DataTable from "@/components/shared/DataTable";
import Faq from "@/components/shared/FAQ";
import Hero from "@/components/shared/Hero";
import HowItWorks from "@/components/shared/HowItWorks";
import Newsletter from "@/components/shared/Newsletter";
import Partners from "@/components/shared/Partners";
import Testimonials from "@/components/shared/Testimonials";
const RootPage = () => {
return (
<>
<Hero />
<HowItWorks />
<Testimonials />
<Partners />
<About />
{/* <DataTable /> */}
<Faq />
<Newsletter />
</>
);
};
export default RootPage;

View file

@ -0,0 +1,30 @@
import { PRIVACY_POLICY } from "@/constants/guidelines";
import React from "react";
import ReactMarkdown from "react-markdown";
import { Metadata } from "next";
export const metadata: Metadata = {
title: "Privacy Policy - SVRJS",
};
const PrivacyPolicy = () => {
return (
<section
id="privacy-policy"
className="wrapper container py-24 md:py-28 gap-2 flex flex-col"
>
<h1 className="text-3xl md:text-5xl pb-1 md:pb-2 font-bold text-black dark:bg-clip-text dark:text-transparent dark:bg-gradient-to-b dark:from-white dark:to-neutral-400">
Privacy Policy
</h1>
<p className="md:text-lg text-muted-foreground text-start mb-6">
Effective date: 26.05.2024
</p>
<div className="prose max-w-full md:prose-lg dark:prose-invert">
<ReactMarkdown>{PRIVACY_POLICY}</ReactMarkdown>
</div>
</section>
);
};
export default PrivacyPolicy;

28
app/(root)/tos/page.tsx Normal file
View file

@ -0,0 +1,28 @@
import ReactMarkdown from "react-markdown";
import { TERMS_AND_CONDITIONS } from "@/constants/guidelines";
import { Metadata } from "next";
export const metadata: Metadata = {
title: "Terms Of Service - SVRJS",
};
const TermsOfService = () => {
return (
<section
id="tos"
className="wrapper container py-24 md:py-28 gap-2 flex flex-col"
>
<h1 className="text-3xl md:text-5xl pb-1 md:pb-2 font-bold text-black dark:bg-clip-text dark:text-transparent dark:bg-gradient-to-b dark:from-white dark:to-neutral-400">
Terms and Conditions
</h1>
<p className="md:text-lg text-muted-foreground text-start mb-6">
Last updated: 24.04.2024
</p>
<div className="prose max-w-full md:prose-lg dark:prose-invert">
<ReactMarkdown>{TERMS_AND_CONDITIONS}</ReactMarkdown>
</div>
</section>
);
};
export default TermsOfService;

View file

@ -0,0 +1,108 @@
"use client";
import ReactMarkdown from "react-markdown";
import { vulnerabilities } from "@/constants/guidelines";
import { useEffect, useState } from "react";
import { Skeleton } from "@/components/ui/skeleton";
interface Bullet {
point: string;
}
interface Vulnerabilities {
_id: string;
date: string;
version: string;
bullets?: Bullet[]; // Make bullets optional
}
const Vulnerabilities = () => {
const [loading, setLoading] = useState(true);
const [downloads, setDownloads] = useState<Vulnerabilities[]>([]);
const [error, setError] = useState("");
const fetchData = async () => {
try {
const response = await fetch("/api/vulnerabilities", {
method: "GET",
});
if (response.ok) {
const data: Vulnerabilities[] = await response.json();
setDownloads(data);
return (document.title = "Vulnerabilities | SVRJS");
} else {
throw new Error(`HTTP error! status: ${response.status}`);
}
} catch (error: any) {
setError(error.message || "Failed to fetch downloads");
} finally {
setLoading(false);
}
};
useEffect(() => {
fetchData();
const interval = setInterval(() => {
fetchData();
}, 10000);
return () => clearInterval(interval);
}, []);
const reversedDownloads = [...downloads].reverse();
// initially loading = true
if (loading) {
return (
<section className="wrapper container py-24 md:py-28 gap-4 flex flex-col">
<div className="mb-3">
<Skeleton className="w-[400px] h-[50px] rounded-md" />
</div>
<div className="flex flex-col gap-4">
<Skeleton className="w-[300px] h-[30px] rounded-md" />
<Skeleton className="w-[200px] h-[20px] rounded-md" />
<Skeleton className="w-[200px] h-[20px] rounded-md" />
<Skeleton className="w-[200px] h-[20px] rounded-md" />
</div>
</section>
);
}
return (
<section
id="vulnerabilities"
className="wrapper container py-24 md:py-28 gap-2 flex flex-col"
>
<h1 className="text-3xl md:text-5xl pb-1 md:pb-2 font-bold text-black dark:bg-clip-text dark:text-transparent dark:bg-gradient-to-b dark:from-white dark:to-neutral-400">
SVR.JS Vulnerabilities
</h1>
<p className="md:text-lg text-muted-foreground text-start mb-6">
Some older versions of SVR.JS are vulnerable to cyberattacks. It&apos;s
recommended to update your SVR.JS version to the newest one. If you find
a security issue with SVR.JS, report it as soon as possible to
vulnerability-reports[at]svrjs[dot]org. We&apos;ll mitigate that
vulnerability if it is possible.
</p>
{error && <p className="text-red-500">{error}</p>}
{reversedDownloads.map((download) => (
<div
key={download._id}
className="flex-start flex-col prose dark:prose-invert mb-4 gap-4"
>
<h2 className="font-semibold text-3xl -mb-2">{download.version}</h2>
<ul className="list-disc pl-5">
{(download.bullets ?? []).map((bullet, index) => (
<li key={index}>{bullet.point}</li>
))}
</ul>
</div>
))}
<div className="prose max-w-full md:prose-lg dark:prose-invert">
<ReactMarkdown>{vulnerabilities}</ReactMarkdown>
</div>
</section>
);
};
export default Vulnerabilities;

View file

@ -0,0 +1,63 @@
import { NextAuthOptions } from "next-auth";
import CredentialsProvider from "next-auth/providers/credentials";
import bcrypt from "bcrypt";
export const authOptions: NextAuthOptions = {
providers: [
CredentialsProvider({
id: "credentials",
name: "Credentials",
credentials: {
username: { label: "Username", type: "text" },
password: { label: "Password", type: "password" },
},
async authorize(credentials: any): Promise<any> {
const adminUsername = process.env.ADMIN_USERNAME;
const adminPasswordHash = process.env.ADMIN_PASSWORD;
console.log(adminUsername);
console.log(adminPasswordHash);
console.log(credentials.username);
console.log(credentials.password);
if (credentials.username == adminUsername) {
const isValidPassword = await bcrypt.compare(
credentials.password,
adminPasswordHash!
);
console.log(isValidPassword);
if (isValidPassword) {
// aany object returned will be saved in `user` property of the jwtt
return { id: 1, name: "svrjsAdmin" };
}
}
// If you return null then an error will be displayed that the user to check their details.
return null;
},
}),
],
callbacks: {
async jwt({ token, user }) {
// Add user info to token
if (user) {
token.id = user.id;
token.name = user.name;
}
return token;
},
async session({ session, token }) {
// Add token info to session
// session.user.id = token.id;
// session.user.name = token.name;
return session;
},
},
pages: {
signIn: "/login",
},
session: {
strategy: "jwt",
},
secret: process.env.NEXTAUTH_SECRET,
};

View file

@ -0,0 +1,6 @@
import NextAuth from "next-auth/next";
import { authOptions } from "./options";
const handler = NextAuth(authOptions);
export { handler as GET, handler as POST };

126
app/api/contact/route.ts Normal file
View file

@ -0,0 +1,126 @@
import { mailOptions, transporter } from "@/lib/nodemailer/nodemailer";
import { NextRequest, NextResponse } from "next/server";
const CONTACT_MESSAGE_FIELDS: Record<string, string> = {
name: "Name",
email: "Email",
message: "Message",
};
const escapeHtml = (text: string) => {
return text
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
};
const generateEmailContent = (data: Record<string, string>) => {
const stringData = Object.entries(data).reduce(
(str, [key, val]) =>
str +
`${CONTACT_MESSAGE_FIELDS[key] || key}: ${val.replace(/\n/g, "\n")} \n\n`,
""
);
const htmlData = Object.entries(data).reduce(
(str, [key, val]) =>
str +
`<h3 class="form-heading">${escapeHtml(
CONTACT_MESSAGE_FIELDS[key] || key
)}</h3><p class="form-answer">${escapeHtml(val).replace(
/\n/g,
"<br/>"
)}</p>`,
""
);
return {
text: stringData,
html: `<!DOCTYPE html>
<html>
<head>
<title>Contact Email</title>
<meta charset="utf-8"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<meta http-equiv="X-UA-Compatible" content="IE=edge"/>
<style type="text/css">
body, table, td, a { -webkit-text-size-adjust: 100%; -ms-text-size-adjust: 100%; }
table { border-collapse: collapse !important; }
body { height: 100% !important; margin: 0 !important; padding: 0 !important; width: 100% !important; }
@media screen and (max-width: 525px) {
.wrapper { width: 100% !important; max-width: 100% !important; }
.responsive-table { width: 100% !important; }
.padding { padding: 10px 5% 15px 5% !important; }
.section-padding { padding: 0 15px 50px 15px !important; }
}
.form-container { margin-bottom: 24px; padding: 20px; border: 1px dashed #ccc; }
.form-heading { color: #2a2a2a; font-family: "Helvetica Neue", "Helvetica", "Arial", sans-serif; font-weight: 400; text-align: left; line-height: 20px; font-size: 18px; margin: 0 0 8px; padding: 0; }
.form-answer { color: #2a2a2a; font-family: "Helvetica Neue", "Helvetica", "Arial", sans-serif; font-weight: 300; text-align: left; line-height: 20px; font-size: 16px; margin: 0 0 24px; padding: 0; }
div[style*="margin: 16px 0;"] { margin: 0 !important; }
</style>
</head>
<body style="margin: 0 !important; padding: 0 !important; background: #fff">
<div style="display: none; font-size: 1px; color: #fefefe; line-height: 1px; max-height: 0px; max-width: 0px; opacity: 0; overflow: hidden;"></div>
<table border="0" cellpadding="0" cellspacing="0" width="100%">
<tr>
<td bgcolor="#ffffff" align="center" style="padding: 10px 15px 30px 15px" class="section-padding">
<table border="0" cellpadding="0" cellspacing="0" width="100%" style="max-width: 500px" class="responsive-table">
<tr>
<td>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td>
<table width="100%" border="0" cellspacing="0" cellpadding="0">
<tr>
<td style="padding: 0 0 0 0; font-size: 16px; line-height: 25px; color: #232323;" class="padding message-content">
<h2>New Contact Message</h2>
<div class="form-container">${htmlData}</div>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</td>
</tr>
</table>
</body>
</html>`,
};
};
export async function POST(req: NextRequest) {
if (req.method !== "POST") {
return NextResponse.json(
{ message: "Method Not Allowed" },
{ status: 405 }
);
}
try {
const data = await req.json();
console.log(data);
await transporter.sendMail({
...mailOptions,
...generateEmailContent(data),
subject: "Contact Email",
});
return NextResponse.json(
{ message: "Email sent successfully" },
{ status: 200 }
);
} catch (error) {
console.error("Error sending email:", error);
return NextResponse.json(
{ message: "Internal Server Error" },
{ status: 500 }
);
}
}

View file

@ -0,0 +1,30 @@
// app/api/delete/[id]/route.ts
import clientPromise from "@/lib/db";
import { ObjectId } from "mongodb";
import { NextResponse } from "next/server";
export async function DELETE(
request: Request,
{ params }: { params: { id: string } }
) {
const { id } = params;
try {
const client = await clientPromise;
const db = client.db("downloadsDatabase");
const collection = db.collection("downloads");
const result = await collection.deleteOne({ _id: new ObjectId(id) });
if (result.deletedCount === 1) {
return NextResponse.json({ message: "Log deleted successfully" });
} else {
return NextResponse.json({ message: "Log not found" }, { status: 404 });
}
} catch (error) {
return NextResponse.json(
{ message: "Failed to delete log", error: error },
{ status: 500 }
);
}
}

View file

@ -0,0 +1,30 @@
// app/api/delete/[id]/route.ts
import clientPromise from "@/lib/db";
import { ObjectId } from "mongodb";
import { NextResponse } from "next/server";
export async function DELETE(
request: Request,
{ params }: { params: { id: string } }
) {
const { id } = params;
try {
const client = await clientPromise;
const db = client.db("downloadsDatabase");
const collection = db.collection("logs");
const result = await collection.deleteOne({ _id: new ObjectId(id) });
if (result.deletedCount === 1) {
return NextResponse.json({ message: "Log deleted successfully" });
} else {
return NextResponse.json({ message: "Log not found" }, { status: 404 });
}
} catch (error) {
return NextResponse.json(
{ message: "Failed to delete log", error: error },
{ status: 500 }
);
}
}

View file

@ -0,0 +1,30 @@
// app/api/delete/[id]/route.ts
import clientPromise from "@/lib/db";
import { ObjectId } from "mongodb";
import { NextResponse } from "next/server";
export async function DELETE(
request: Request,
{ params }: { params: { id: string } }
) {
const { id } = params;
try {
const client = await clientPromise;
const db = client.db("downloadsDatabase");
const collection = db.collection("mods");
const result = await collection.deleteOne({ _id: new ObjectId(id) });
if (result.deletedCount === 1) {
return NextResponse.json({ message: "Log deleted successfully" });
} else {
return NextResponse.json({ message: "Log not found" }, { status: 404 });
}
} catch (error) {
return NextResponse.json(
{ message: "Failed to delete log", error: error },
{ status: 500 }
);
}
}

View file

@ -0,0 +1,22 @@
import { NextRequest, NextResponse } from "next/server";
import clientPromise from "@/lib/db";
// Force the API to use SSR instead of static generation
export const dynamic = "force-dynamic";
// 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();
// console.log("Downloads fetched:", downloads);
return NextResponse.json(downloads, { status: 200 });
} catch (error) {
console.log(`Error Messge ${error}`);
return NextResponse.json(
{ error: "Failed to fetch downloads" },
{ status: 500 }
);
}
}

34
app/api/login/route.ts Normal file
View file

@ -0,0 +1,34 @@
import { NextRequest, NextResponse } from "next/server";
import { serialize } from "cookie";
// Force the API to use SSR instead of static generation
export const dynamic = "force-dynamic";
export async function POST(request: NextRequest) {
const { username, password } = await request.json();
const adminUsername = process.env.ADMIN_USERNAME;
const adminPassword = process.env.ADMIN_PASSWORD;
if (username === adminUsername && password === adminPassword) {
const cookie = serialize("auth", "authenticated", {
httpOnly: true,
path: "/",
maxAge: 60 * 60 * 24, // 1 day
});
return new NextResponse(JSON.stringify({ message: "Login successful" }), {
headers: {
"Set-Cookie": cookie,
"Content-Type": "application/json",
},
});
}
return new NextResponse(JSON.stringify({ message: "Invalid credentials" }), {
status: 401,
headers: {
"Content-Type": "application/json",
},
});
}

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

@ -0,0 +1,20 @@
import { NextRequest, NextResponse } from "next/server";
import clientPromise from "@/lib/db";
// Force the API to use SSR instead of static generation
export const dynamic = "force-dynamic";
// 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("logs").find().toArray();
return NextResponse.json(downloads, { status: 200 });
} catch (error) {
return NextResponse.json(
{ error: "Failed to fetch logs" },
{ status: 500 }
);
}
}

View file

@ -0,0 +1,127 @@
import { NextRequest, NextResponse } from "next/server";
import clientPromise from "@/lib/db";
export const GET = async (
req: NextRequest,
{ params }: { params: { slug: string } }
) => {
const client = await clientPromise;
const db = client.db();
const { slug } = params;
if (!slug) {
return NextResponse.json({ message: "Slug is required" }, { status: 400 });
}
const page = await db.collection("pages").findOne({ slug });
if (page) {
return NextResponse.json(page, { status: 200 });
} else {
return NextResponse.json({ message: "Page not found" }, { status: 404 });
}
};
export const PUT = async (
req: NextRequest,
{ params }: { params: { slug: string } }
) => {
const client = await clientPromise;
const db = client.db();
const { slug } = params;
if (!slug) {
return NextResponse.json({ message: "Slug is required" }, { status: 400 });
}
const { title, content } = await req.json();
if (typeof title !== "string" || typeof content !== "string") {
return NextResponse.json(
{ message: "Invalid title or content" },
{ status: 400 }
);
}
try {
// it works here ig
const result = await db
.collection("pages")
.findOneAndUpdate(
{ slug },
{ $set: { title, content } },
{ returnDocument: "after" }
);
// i hate my life fr fr
console.log("Update Result:", result);
// result returns like
// Update Result: {
// _id: new ObjectId('66a2946b2b91eef505eef943'),
// title: 'TEST PAGE',
// slug: 'test-page',
// content: 'asd]---\n' +
// '---\n' +
// '\n' +
// 'this is basic heading ?\n' +
// '\n' +
// '**HELLO**\n' +
// '\n' +
// 'erw\n' +
// '\n' +
// 'trying another time for test'
// }
// ERRROR : TypeError: Cannot read properties of undefined (reading '_id')
// aposdjaoi sdio JUST WORK NIAWWWWWWWWW
// if (result && result.value) {
const serializedResult = {
...result?.value,
_id: result?.value._id.toString(), // Convert ObjectId to string
};
return NextResponse.json(result?.value.content, { status: 200 });
// } else {
// return NextResponse.json({ message: "Page not found" }, { status: 404 });
// }
} catch (error) {
console.error("Error updating page:", error);
return NextResponse.json(
{ message: "Failed to update page" },
{ status: 500 }
);
}
};
export const DELETE = async (
req: NextRequest,
{ params }: { params: { slug: string } }
) => {
const client = await clientPromise;
const db = client.db();
const { slug } = params;
if (!slug) {
return NextResponse.json({ message: "Slug is required" }, { status: 400 });
}
try {
const result = await db.collection("pages").deleteOne({ slug });
if (result.deletedCount > 0) {
return NextResponse.json(
{ message: "Page deleted successfully" },
{ status: 200 }
);
} else {
return NextResponse.json({ message: "Page not found" }, { status: 404 });
}
} catch (error) {
console.error("Error deleting page:", error);
return NextResponse.json(
{ message: "Failed to delete page" },
{ status: 500 }
);
}
};

View file

@ -0,0 +1,43 @@
import { NextRequest, NextResponse } from "next/server";
import clientPromise from "@/lib/db";
export const GET = async (req: NextRequest) => {
const client = await clientPromise;
const db = client.db();
try {
const pages = await db.collection("pages").find().toArray();
return NextResponse.json(pages, { status: 200 });
} catch (error) {
console.error("Error fetching pages:", error);
return NextResponse.json(
{ message: "Failed to fetch pages" },
{ status: 500 }
);
}
};
export const POST = async (req: NextRequest) => {
const client = await clientPromise;
const db = client.db();
const { title, slug, content } = await req.json();
if (!slug) {
return NextResponse.json(
{ message: "Missing required fields" },
{ status: 400 }
);
}
try {
const newPage = { title, slug, content };
const result = await db.collection("pages").insertOne(newPage);
return NextResponse.json(newPage, { status: 201 });
} catch (error) {
console.error("Error creating page:", error);
return NextResponse.json(
{ message: "Failed to create page" },
{ status: 500 }
);
}
};

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

@ -0,0 +1,20 @@
import { NextRequest, NextResponse } from "next/server";
import clientPromise from "@/lib/db";
// Force the API to use SSR instead of static generation
export const dynamic = "force-dynamic";
// 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("mods").find().toArray();
return NextResponse.json(downloads, { status: 200 });
} catch (error) {
return NextResponse.json(
{ error: "Failed to fetch mods" },
{ status: 500 }
);
}
}

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

@ -0,0 +1,23 @@
import { NextResponse } from "next/server";
import clientPromise from "@/lib/db";
// Force the API to use SSR instead of static generation
export const dynamic = "force-dynamic";
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,21 @@
import { NextResponse } from "next/server";
import clientPromise from "@/lib/db";
// Force the API to use SSR instead of static generation
export const dynamic = "force-dynamic";
export async function POST(request: Request) {
const body = await request.json();
const { version, date, bullets } = body;
const client = await clientPromise;
const db = client.db("downloadsDatabase");
const result = await db.collection("logs").insertOne({
version,
date,
bullets,
});
return NextResponse.json({ success: true, id: result.insertedId });
}

View file

@ -0,0 +1,23 @@
import { NextResponse } from "next/server";
import clientPromise from "@/lib/db";
// Force the API to use SSR instead of static generation
export const dynamic = "force-dynamic";
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("mods").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,9 @@
import { createRouteHandler } from "uploadthing/next";
import { ourFileRouter } from "./core";
// Force the API to use SSR instead of static generation
export const dynamic = "force-dynamic";
export const { GET, POST } = createRouteHandler({
router: ourFileRouter,
});

View file

@ -0,0 +1,20 @@
import { NextResponse } from "next/server";
import clientPromise from "@/lib/db";
// Force the API to use SSR instead of static generation
export const dynamic = "force-dynamic";
export async function POST(request: Request) {
const body = await request.json();
const { version, date, bullets } = body;
const client = await clientPromise;
const db = client.db("downloadsDatabase");
const result = await db.collection("vulnerabilities").insertOne({
version,
bullets,
});
return NextResponse.json({ success: true, id: result.insertedId });
}

View file

@ -0,0 +1,20 @@
import { NextRequest, NextResponse } from "next/server";
import clientPromise from "@/lib/db";
// Force the API to use SSR instead of static generation
export const dynamic = "force-dynamic";
// 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("vulnerabilities").find().toArray();
return NextResponse.json(downloads, { status: 200 });
} catch (error) {
return NextResponse.json(
{ error: "Failed to fetch logs" },
{ status: 500 }
);
}
}

BIN
app/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

261
app/globals.css Normal file
View file

@ -0,0 +1,261 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
html {
height: 100%;
width: 100%;
font-family: "Poppins", sans-serif;
}
body {
user-select: text;
}
* {
padding: 0;
margin: 0;
scroll-behavior: smooth;
}
@layer base {
:root {
--svg-fill: white;
--svg-background: black;
--background: 0 0% 100%;
--foreground: 240 10% 3.9%;
--card: 0 0% 100%;
--card-foreground: 240 10% 3.9%;
--popover: 0 0% 100%;
--popover-foreground: 240 10% 3.9%;
--primary: 142.1 76.2% 36.3%;
--primary-foreground: 355.7 100% 97.3%;
--secondary: 240 4.8% 95.9%;
--secondary-foreground: 240 5.9% 10%;
--muted: 240 4.8% 95.9%;
--muted-foreground: 240 3.8% 46.1%;
--accent: 240 4.8% 95.9%;
--accent-foreground: 240 5.9% 10%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 0 0% 98%;
--border: 240 5.9% 90%;
--input: 240 5.9% 90%;
--ring: 142.1 76.2% 36.3%;
--radius: 0.5rem;
}
.dark {
--svg-fill: black;
--svg-background: white;
--background: 20 14.3% 4.1%;
--foreground: 0 0% 95%;
--card: 24 9.8% 10%;
--card-foreground: 0 0% 95%;
--popover: 0 0% 9%;
--popover-foreground: 0 0% 95%;
--primary: 142.1 70.6% 45.3%;
--primary-foreground: 144.9 80.4% 10%;
--secondary: 240 3.7% 15.9%;
--secondary-foreground: 0 0% 98%;
--muted: 0 0% 15%;
--muted-foreground: 240 5% 64.9%;
--accent: 12 6.5% 15.1%;
--accent-foreground: 0 0% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 0 85.7% 97.3%;
--border: 240 3.7% 15.9%;
--input: 240 3.7% 15.9%;
--ring: 142.4 71.8% 29.2%;
}
}
@layer base {
* {
@apply border-border;
}
body {
@apply bg-background text-foreground w-full h-full;
}
}
@layer utilities {
.wrapper {
@apply max-w-screen-xl lg:mx-auto p-5 md:px-10 xl:px-0 w-full;
}
.flex-center {
@apply flex justify-center items-center;
}
.flex-between {
@apply flex justify-between items-center;
}
.flex-start {
@apply flex justify-between items-start;
}
.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 */
.h1-bold {
@apply font-bold text-[40px] leading-[48px] lg:text-[48px] lg:leading-[60px] xl:text-[58px] xl:leading-[74px];
}
/* 40 */
.h2-bold {
@apply font-bold text-[32px] leading-[40px] lg:text-[36px] lg:leading-[44px] xl:text-[40px] xl:leading-[48px];
}
.h2-medium {
@apply font-medium text-[32px] leading-[40px] lg:text-[36px] lg:leading-[44px] xl:text-[40px] xl:leading-[48px];
}
/* 36 */
.h3-bold {
@apply font-bold text-[28px] leading-[36px] md:text-[36px] md:leading-[44px];
}
.h3-medium {
@apply font-medium text-[28px] leading-[36px] md:text-[36px] md:leading-[44px];
}
/* 32 */
.h4-medium {
@apply font-medium text-[32px] leading-[40px];
}
/* 28 */
.h5-bold {
@apply font-bold text-[28px] leading-[36px];
}
/* 24 */
.p-bold-24 {
@apply font-bold text-[24px] leading-[36px];
}
.p-medium-24 {
@apply font-medium text-[24px] leading-[36px];
}
.p-regular-24 {
@apply font-normal text-[24px] leading-[36px];
}
/* 20 */
.p-bold-20 {
@apply font-bold text-[20px] leading-[30px] tracking-[2%];
}
.p-semibold-20 {
@apply text-[20px] font-semibold leading-[30px] tracking-[2%];
}
.p-medium-20 {
@apply text-[20px] font-medium leading-[30px];
}
.p-regular-20 {
@apply text-[20px] font-normal leading-[30px] tracking-[2%];
}
/* 18 */
.p-semibold-18 {
@apply text-[18px] font-semibold leading-[28px] tracking-[2%];
}
.p-medium-18 {
@apply text-[18px] font-medium leading-[28px];
}
.p-regular-18 {
@apply text-[18px] font-normal leading-[28px] tracking-[2%];
}
/* 16 */
.p-bold-16 {
@apply text-[16px] font-bold leading-[24px];
}
.p-medium-16 {
@apply text-[16px] font-medium leading-[24px];
}
.p-regular-16 {
@apply text-[16px] font-normal leading-[24px];
}
/* 14 */
.p-semibold-14 {
@apply text-[14px] font-semibold leading-[20px];
}
.p-medium-14 {
@apply text-[14px] font-medium leading-[20px];
}
.p-regular-14 {
@apply text-[14px] font-normal leading-[20px];
}
/* 12 */
.p-medium-12 {
@apply text-[12px] font-medium leading-[20px];
}
.toggle-switch {
@apply bg-gray-300 !important;
}
}

40
app/layout.tsx Normal file
View file

@ -0,0 +1,40 @@
import type { Metadata } from "next";
import { Poppins } from "next/font/google";
import "./globals.css";
import { ThemeProvider } from "@/components/shared/providers/themeprovider";
import AuthProvider from "@/components/shared/providers/AuthProvider";
import { Toaster } from "@/components/ui/toaster";
const poppins = Poppins({
weight: ["400", "600", "700", "900"],
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "SVRJS - A Web Server running on Nodejs",
description: "Open Source Software Library",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" suppressHydrationWarning>
<body className={`antialiased ${poppins.className}`}>
<ThemeProvider
attribute="class"
defaultTheme="dark"
enableSystem
disableTransitionOnChange
>
<AuthProvider>
{children}
<Toaster />
</AuthProvider>
</ThemeProvider>
</body>
</html>
);
}

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

@ -0,0 +1,76 @@
"use client";
import React, { useState } from "react";
import { signIn } from "next-auth/react";
import { useRouter } from "next/navigation";
import { Button } from "@/components/ui/button";
import { useSession } from "next-auth/react";
const LoginPage = () => {
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const router = useRouter();
const { data: session, status } = useSession();
if (status === "loading") {
return (
<>
<main className="h-screen w-full flex-center">
<p className="animate-pulse text-xl">Loading</p>
</main>
</>
);
}
if (session) {
router.push("/admin");
}
const handleLogin = async (e: React.FormEvent) => {
e.preventDefault();
const res = await signIn("credentials", {
redirect: false,
username,
password,
});
if (res?.ok) {
router.push("/admin");
} else {
setError("Invalid credentials");
}
};
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;

19
app/not-found.tsx Normal file
View file

@ -0,0 +1,19 @@
import Link from "next/link";
const NotFound = () => {
return (
<section id="404error" className="flex-center flex-col wrapper container">
<h1 className="text-3xl md:text-5xl text-center">
<span className="text-red-500">404</span> Page not Found
</h1>
<p className="text-lg mt-3 text-muted-foreground">
Please return back to{" "}
<Link href="/" className="underline font-bold">
Home
</Link>
</p>
</section>
);
};
export default NotFound;

20
app/sitemap.ts Normal file
View file

@ -0,0 +1,20 @@
export default async function sitemap() {
let routes = [
"",
"/blog",
"/changelogs",
"/contact",
"/contribute",
"/downloads",
"/forum",
"/mods",
"/privacy-policy",
"/tos",
"/vulnerabilities",
].map((route) => ({
url: `https://vimfn.in${route}`,
lastModified: new Date().toISOString().split("T")[0],
}));
return [...routes];
}

BIN
bun.lockb Normal file

Binary file not shown.

17
components.json Normal file
View file

@ -0,0 +1,17 @@
{
"$schema": "https://ui.shadcn.com/schema.json",
"style": "default",
"rsc": true,
"tsx": true,
"tailwind": {
"config": "tailwind.config.ts",
"css": "app/globals.css",
"baseColor": "slate",
"cssVariables": true,
"prefix": ""
},
"aliases": {
"components": "@/components",
"utils": "@/lib/utils"
}
}

View file

@ -0,0 +1,55 @@
import Image from "next/image";
import React from "react";
interface TestimonialCard {
avatar: string;
name: string;
role?: string;
testimonial: string;
rating: number;
}
const TestimonialCard = ({
avatar,
name,
role,
testimonial,
rating,
}: TestimonialCard) => {
return (
<li className="inline-block w-full">
<div className="bg-primary/10 dark:bg-[#1c1c1c] mx-auto mb-5 flex w-full cursor-default flex-col gap-4 rounded-2xl px-[30px] py-6 shadow-md transition-all hover:scale-[103%]">
<div className="flex flex-row items-center gap-3">
<div>
<Image
src={`/testimonials/${avatar}.webp`}
alt="avatar1"
width={40}
height={40}
className="rounded-full"
/>
</div>
<div className="space-y-1">
<div className="flex items-center gap-1">
<div className="small-semibold dark:text-white">{name}</div>
</div>
<div className="text-sm text-muted-foreground">{role}</div>
</div>
</div>
<p className="body-regular dark:text-white">{testimonial}</p>
<div className="hue-rotate-90 text-lg">
{/* <Image
src="/testimonials/stars.svg"
alt="star"
width={120}
height={120}
className="object-cover"
/> */}
{"⭐".repeat(rating)}
</div>
</div>
</li>
);
};
export default TestimonialCard;

View file

@ -0,0 +1,42 @@
import Image from "next/image";
import React from "react";
import Statistics from "./Statistics";
const About = () => {
return (
<section id="about" className="container py-2 sm:py-9">
<div className="bg-accent/50 border rounded-lg py-12">
<div className="px-6 flex flex-col-reverse md:flex-row gap-8 md:gap-12">
<Image
src="/about.svg"
alt="aboutpicture"
width={300}
height={300}
className="w-[300px] object-contain rounded-lg flex-shrink-0"
/>
<div className="flex flex-col justify-between">
<div className="pb-6">
<h2 className="text-3xl md:text-5xl font-bold">
About{" "}
<span className="bg-gradient-to-b from-green-300 to-primary text-transparent bg-clip-text">
SVRJS!
</span>
</h2>
<p className="text-lg text-muted-foreground mt-4">
Host a webpage, run server-side JavaScript, use mods to expand
server functionality, or use it as a forward or reverse proxy.
SVRJS is a web server that runs on top of Node.JS, enabling
server-side JS on webpages. SVRJS also has an integrated log
viewer, log highlighter, and user management tool.
</p>
</div>
<Statistics />
</div>
</div>
</div>
</section>
);
};
export default About;

View file

@ -0,0 +1,11 @@
import React from "react";
const DataTable = () => {
return (
<section id="datatable" className="wrapper container py-2 sm:py-9">
DataTable
</section>
);
};
export default DataTable;

39
components/shared/FAQ.tsx Normal file
View file

@ -0,0 +1,39 @@
import { questions } from "@/constants";
import React from "react";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "../ui/accordion";
const Faq = () => {
return (
<section id="faq" className="wrapper container py-24 md:py-28">
<h2 className="text-3xl md:text-5xl font-bold mb-4 md:mb-2 md:pb-2 text-black dark:bg-clip-text dark:text-transparent dark:bg-gradient-to-b dark:from-white dark:to-neutral-400">
Frequently Asked Question
</h2>
<p className="text-lg text-muted-foreground text-start mt-4 md:mt-2 mb-8">
Find answers to common questions about SVRJS
</p>
<Accordion
type="single"
collapsible={true}
className="w-full AccordionRoot"
>
{questions.map(({ question, answer, key }) => (
<AccordionItem key={key} value={key} className="border-b">
<AccordionTrigger className="text-left text-lg">
{question}
</AccordionTrigger>
<AccordionContent className="text-[1rem] text-muted-foreground">
{answer}
</AccordionContent>
</AccordionItem>
))}
</Accordion>
</section>
);
};
export default Faq;

View file

@ -0,0 +1,107 @@
import Image from "next/image";
import Link from "next/link";
import React from "react";
import Iconss from "../ui/icons";
import { FOOTERLINKS } from "@/constants";
import Logo from "./Logo";
const Footer = () => {
const currentYear = new Date().getFullYear();
return (
<>
<footer className="flex flex-col w-full transition-all bg-zinc-50 text-black dark:bg-[#0308033b] border-t dark:text-white">
<div className="px-6 md:px-12 lg:px-24 py-10 w-full mx-auto max-w-screen-2xl">
<div className="flex flex-col lg:flex-row justify-between max-lg:items-start max-md:items-center items-center mb-6 ">
<div className="flex items-center mb-6 lg:mb-0">
<Logo width={200} height={80} />
</div>
<div className="flex flex-col items-center md:items-start md:flex-row justify-between w-full lg:w-auto space-y-6 md:space-y-0 md:space-x-6 xl:space-x-16">
<div className="flex flex-col items-center md:items-start">
<div className="text-2xl font-light text-primary">
Quick Links
</div>
{FOOTERLINKS.otherPages.map((link) => (
<span key={link.href}>
<Link
href={link.href}
className="text-base font-light dark:hover:text-green-100/70 hover:text-green-500 hover:underline"
>
{link.label}
</Link>
</span>
))}
</div>
<div className="flex flex-col items-center md:items-start">
<h1 className="text-2xl font-light text-primary">Resources</h1>
{FOOTERLINKS.plans.map((link) => (
<span key={link.href}>
<Link
href={link.href}
className="text-base font-light dark:hover:text-green-100/70 hover:text-green-500 hover:underline"
>
{link.label}
</Link>
</span>
))}
</div>
<div className="flex flex-col items-center md:items-start">
<div className="text-2xl font-light text-primary">
Additional
</div>
{FOOTERLINKS.additional.map((link) => (
<span key={link.href}>
<Link
href={link.href}
className="text-base font-light dark:hover:text-green-100/70 hover:text-green-500 hover:underline"
>
{link.label}
</Link>
</span>
))}
</div>
<div className="flex flex-col items-center md:items-start">
<div className="text-2xl font-light text-primary">Social</div>
<p className="text-base font-light">
{FOOTERLINKS.social.supportText}
</p>
<div className="flex space-x-1 py-3">
<Iconss />
</div>
</div>
</div>
</div>
<div className="border-t mb-6 border-gray-300 dark:border-white/30"></div>
<div className="flex flex-col lg:flex-row justify-between items-center space-y-4 lg:space-y-0 px-4">
<span className="text-sm font-light">
Copyright © 2023-{currentYear}{" "}
<Link
href={FOOTERLINKS.footerBottom.rightsReserved.href}
className="text-primary font-semibold"
>
{FOOTERLINKS.footerBottom.rightsReserved.label}
</Link>
</span>
<span className="text-sm font-light">
<Link
href={FOOTERLINKS.footerBottom.termsofService.href}
className="text-primary font-medium transition-all underline-offset-4 hover:underline"
>
{FOOTERLINKS.footerBottom.termsofService.label}{" "}
</Link>
|{" "}
<Link
href={FOOTERLINKS.footerBottom.privacyPolicy.href}
className="text-primary font-medium transition-all underline-offset-4 hover:underline"
>
{FOOTERLINKS.footerBottom.privacyPolicy.label}
</Link>
</span>
</div>
</div>
</footer>
</>
);
};
export default Footer;

170
components/shared/Hero.tsx Normal file
View file

@ -0,0 +1,170 @@
"use client";
import React, { useState } from "react";
import HeroCards from "./HeroCards";
import { Button } from "../ui/button";
import Link from "next/link";
import { Check, Clipboard } from "lucide-react";
import GridPattern from "../ui/grid-pattern";
import AnimatedGradientText from "../ui/animated-gradient-text";
import { Happy_Monkey } from "next/font/google";
import { cn } from "@/lib/utils";
import Image from "next/image";
const happyMonkey = Happy_Monkey({
preload: true,
weight: ["400"],
subsets: ["latin"],
});
const Hero = () => {
const [isCopied, setIsCopied] = useState(false);
const [command, setCommand] = useState(
"curl -fsSL https://downloads.svrjs.org/installer/svr.js.installer.linux.20240509.sh > /tmp/installer.sh && sudo bash /tmp/installer.sh"
);
const [selectedButton, setSelectedButton] = useState<"linux" | "docker">(
"linux"
);
const commands = {
linux:
"curl -fsSL https://downloads.svrjs.org/installer/svr.js.installer.linux.20240509.sh > /tmp/installer.sh && sudo bash /tmp/installer.sh",
docker:
"docker pull svrjs/svrjs && docker run --name mysvrjs -d -p 80:80 --restart=always svrjs/svrjs",
};
const copyToClipboard = () => {
navigator.clipboard.writeText(command);
setIsCopied(true);
setTimeout(() => setIsCopied(false), 2000);
};
const handleButtonClick = (type: "linux" | "docker") => {
setCommand(commands[type]);
setSelectedButton(type);
};
return (
<section className="relative sm:container grid lg:grid-cols-2 place-items-center py-20 md:py-24 gap-10">
<GridPattern
className={cn(
"[mask-image:radial-gradient(300px_circle_at_center,white,transparent)]",
"inset-x-0 inset-y-[-50%] h-[200%] opacity-30"
)}
/>
<div className="text-center lg:text-start space-y-6">
<AnimatedGradientText className="mx-auto lg:mx-0">
🎉{" "}
<hr className="mx-2 h-4 w-[1px] shrink-0 bg-black dark:bg-gray-300" />
<span
className={cn(
`inline animate-gradient bg-gradient-to-r from-[#235b1a] to-[#315620] dark:bg-gradient-to-r dark:from-[#6df458] dark:to-[#4c932a] bg-[length:var(--bg-size)_100%] bg-clip-text text-transparent`
)}
>
Expanding server functionality
</span>
</AnimatedGradientText>
<main className="text-5xl md:text-6xl font-bold">
<h1 className="inline custom-title">
<span className="text-transparent bg-gradient-to-r from-green-300 to-primary bg-clip-text">
Simplify
</span>{" "}
your server logic performance
</h1>
</main>
<p className="text-lg text-muted-foreground md:w-10/12 mx-auto lg:mx-0">
SVR.JS is a web server that runs on top of Node.JS, thus enabling
server-side JavaScript on webpages. SVR.JS also has an integrated log
viewer and more...
</p>
<div className="relative mx-auto lg:mx-0 flex gap-2 flex-col-reverse lg:flex-row justify-start items-center w-fit">
<Button
className="w-fit"
onClick={copyToClipboard}
variant={!isCopied ? "secondary" : "secondary"}
>
{!isCopied ? (
<Clipboard className="w-4 h-4 mr-2" />
) : (
<Check className="w-4 h-4 mr-2" />
)}
{command.slice(0, 39)}...
</Button>
<p className="hidden lg:block">|</p>
<p className="block lg:hidden">or</p>
<Link className="w-full" href="/downloads">
<Button className="w-full">Download</Button>
</Link>
<div className="pointer-events-none dark:invert -scale-x-100 absolute -bottom-14 max-lg:left-0 lg:right-20 inline-flex justify-center items-center gap-1">
<Image
src="/curly-arrow.png"
width={35}
height={35}
alt="Curly arrow"
/>
<span
className={cn(
`mt-10 font-bold text-black -scale-x-100 text-sm ${happyMonkey.className}`
)}
>
Try Now!
</span>
</div>
</div>
<div className="flex items-center lg:justify-start justify-center gap-3 w-full">
<Button
className={`rounded-full w-12 h-12 lg:w-16 lg:h-16 ${
selectedButton === "linux"
? "bg-accent"
: "bg-primary-foreground/20"
}`}
variant={"ghost"}
onClick={() => handleButtonClick("linux")}
>
<span className="sr-only">Linux</span>
<svg
xmlns="http://www.w3.org/2000/svg"
width={200}
height={200}
viewBox="0 0 448 512"
fill="none"
>
<path
fill="currentColor"
d="M220.8 123.3c1 .5 1.8 1.7 3 1.7 1.1 0 2.8-.4 2.9-1.5.2-1.4-1.9-2.3-3.2-2.9-1.7-.7-3.9-1-5.5-.1-.4.2-.8.7-.6 1.1.3 1.3 2.3 1.1 3.4 1.7zm-21.9 1.7c1.2 0 2-1.2 3-1.7 1.1-.6 3.1-.4 3.5-1.6.2-.4-.2-.9-.6-1.1-1.6-.9-3.8-.6-5.5.1-1.3.6-3.4 1.5-3.2 2.9.1 1 1.8 1.5 2.8 1.4zM420 403.8c-3.6-4-5.3-11.6-7.2-19.7-1.8-8.1-3.9-16.8-10.5-22.4-1.3-1.1-2.6-2.1-4-2.9-1.3-.8-2.7-1.5-4.1-2 9.2-27.3 5.6-54.5-3.7-79.1-11.4-30.1-31.3-56.4-46.5-74.4-17.1-21.5-33.7-41.9-33.4-72C311.1 85.4 315.7.1 234.8 0 132.4-.2 158 103.4 156.9 135.2c-1.7 23.4-6.4 41.8-22.5 64.7-18.9 22.5-45.5 58.8-58.1 96.7-6 17.9-8.8 36.1-6.2 53.3-6.5 5.8-11.4 14.7-16.6 20.2-4.2 4.3-10.3 5.9-17 8.3s-14 6-18.5 14.5c-2.1 3.9-2.8 8.1-2.8 12.4 0 3.9.6 7.9 1.2 11.8 1.2 8.1 2.5 15.7.8 20.8-5.2 14.4-5.9 24.4-2.2 31.7 3.8 7.3 11.4 10.5 20.1 12.3 17.3 3.6 40.8 2.7 59.3 12.5 19.8 10.4 39.9 14.1 55.9 10.4 11.6-2.6 21.1-9.6 25.9-20.2 12.5-.1 26.3-5.4 48.3-6.6 14.9-1.2 33.6 5.3 55.1 4.1.6 2.3 1.4 4.6 2.5 6.7v.1c8.3 16.7 23.8 24.3 40.3 23 16.6-1.3 34.1-11 48.3-27.9 13.6-16.4 36-23.2 50.9-32.2 7.4-4.5 13.4-10.1 13.9-18.3.4-8.2-4.4-17.3-15.5-29.7zM223.7 87.3c9.8-22.2 34.2-21.8 44-.4 6.5 14.2 3.6 30.9-4.3 40.4-1.6-.8-5.9-2.6-12.6-4.9 1.1-1.2 3.1-2.7 3.9-4.6 4.8-11.8-.2-27-9.1-27.3-7.3-.5-13.9 10.8-11.8 23-4.1-2-9.4-3.5-13-4.4-1-6.9-.3-14.6 2.9-21.8zM183 75.8c10.1 0 20.8 14.2 19.1 33.5-3.5 1-7.1 2.5-10.2 4.6 1.2-8.9-3.3-20.1-9.6-19.6-8.4.7-9.8 21.2-1.8 28.1 1 .8 1.9-.2-5.9 5.5-15.6-14.6-10.5-52.1 8.4-52.1zm-13.6 60.7c6.2-4.6 13.6-10 14.1-10.5 4.7-4.4 13.5-14.2 27.9-14.2 7.1 0 15.6 2.3 25.9 8.9 6.3 4.1 11.3 4.4 22.6 9.3 8.4 3.5 13.7 9.7 10.5 18.2-2.6 7.1-11 14.4-22.7 18.1-11.1 3.6-19.8 16-38.2 14.9-3.9-.2-7-1-9.6-2.1-8-3.5-12.2-10.4-20-15-8.6-4.8-13.2-10.4-14.7-15.3-1.4-4.9 0-9 4.2-12.3zm3.3 334c-2.7 35.1-43.9 34.4-75.3 18-29.9-15.8-68.6-6.5-76.5-21.9-2.4-4.7-2.4-12.7 2.6-26.4v-.2c2.4-7.6.6-16-.6-23.9-1.2-7.8-1.8-15 .9-20 3.5-6.7 8.5-9.1 14.8-11.3 10.3-3.7 11.8-3.4 19.6-9.9 5.5-5.7 9.5-12.9 14.3-18 5.1-5.5 10-8.1 17.7-6.9 8.1 1.2 15.1 6.8 21.9 16l19.6 35.6c9.5 19.9 43.1 48.4 41 68.9zm-1.4-25.9c-4.1-6.6-9.6-13.6-14.4-19.6 7.1 0 14.2-2.2 16.7-8.9 2.3-6.2 0-14.9-7.4-24.9-13.5-18.2-38.3-32.5-38.3-32.5-13.5-8.4-21.1-18.7-24.6-29.9s-3-23.3-.3-35.2c5.2-22.9 18.6-45.2 27.2-59.2 2.3-1.7.8 3.2-8.7 20.8-8.5 16.1-24.4 53.3-2.6 82.4.6-20.7 5.5-41.8 13.8-61.5 12-27.4 37.3-74.9 39.3-112.7 1.1.8 4.6 3.2 6.2 4.1 4.6 2.7 8.1 6.7 12.6 10.3 12.4 10 28.5 9.2 42.4 1.2 6.2-3.5 11.2-7.5 15.9-9 9.9-3.1 17.8-8.6 22.3-15 7.7 30.4 25.7 74.3 37.2 95.7 6.1 11.4 18.3 35.5 23.6 64.6 3.3-.1 7 .4 10.9 1.4 13.8-35.7-11.7-74.2-23.3-84.9-4.7-4.6-4.9-6.6-2.6-6.5 12.6 11.2 29.2 33.7 35.2 59 2.8 11.6 3.3 23.7.4 35.7 16.4 6.8 35.9 17.9 30.7 34.8-2.2-.1-3.2 0-4.2 0 3.2-10.1-3.9-17.6-22.8-26.1-19.6-8.6-36-8.6-38.3 12.5-12.1 4.2-18.3 14.7-21.4 27.3-2.8 11.2-3.6 24.7-4.4 39.9-.5 7.7-3.6 18-6.8 29-32.1 22.9-76.7 32.9-114.3 7.2zm257.4-11.5c-.9 16.8-41.2 19.9-63.2 46.5-13.2 15.7-29.4 24.4-43.6 25.5s-26.5-4.8-33.7-19.3c-4.7-11.1-2.4-23.1 1.1-36.3 3.7-14.2 9.2-28.8 9.9-40.6.8-15.2 1.7-28.5 4.2-38.7 2.6-10.3 6.6-17.2 13.7-21.1.3-.2.7-.3 1-.5.8 13.2 7.3 26.6 18.8 29.5 12.6 3.3 30.7-7.5 38.4-16.3 9-.3 15.7-.9 22.6 5.1 9.9 8.5 7.1 30.3 17.1 41.6 10.6 11.6 14 19.5 13.7 24.6zM173.3 148.7c2 1.9 4.7 4.5 8 7.1 6.6 5.2 15.8 10.6 27.3 10.6 11.6 0 22.5-5.9 31.8-10.8 4.9-2.6 10.9-7 14.8-10.4s5.9-6.3 3.1-6.6-2.6 2.6-6 5.1c-4.4 3.2-9.7 7.4-13.9 9.8-7.4 4.2-19.5 10.2-29.9 10.2s-18.7-4.8-24.9-9.7c-3.1-2.5-5.7-5-7.7-6.9-1.5-1.4-1.9-4.6-4.3-4.9-1.4-.1-1.8 3.7 1.7 6.5z"
/>
</svg>
</Button>
<Button
className={`rounded-full w-12 h-12 lg:w-16 lg:h-16 ${
selectedButton === "docker"
? "bg-accent"
: "bg-primary-foreground/20"
}`}
variant={"ghost"}
onClick={() => handleButtonClick("docker")}
>
<span className="sr-only">Docker</span>
<svg
xmlns="http://www.w3.org/2000/svg"
width={200}
height={200}
viewBox="0 0 50 50"
fill="none"
>
<path
fill="currentColor"
d="M27.336 23.076h-5.164v-5.8h5.164v5.8Zm0-19.951h-5.164v5.928h5.164V3.125Zm6.11 14.14H28.28v5.801h5.164v-5.8Zm-12.212-7.04H16.07v5.869h5.164v-5.87Zm6.102 0h-5.164v5.869h5.164v-5.87ZM48.96 19.99c-1.125-.947-3.719-1.289-5.711-.82-.258-2.344-1.305-4.385-3.211-6.22l-1.094-.909-.726 1.367c-1.438 2.715-1.828 7.188-.29 10.137-.679.459-2.015 1.084-3.78 1.045H.186c-.68 4.96.454 11.406 3.438 15.83 2.898 4.287 7.242 6.465 12.922 6.465 12.297 0 21.398-7.08 25.656-19.942 1.672.04 5.281.01 7.133-4.414.117-.244.516-1.289.664-1.67l-1.04-.869ZM9.03 17.266H3.875v5.8h5.164v-5.8h-.008Zm6.102 0H9.969v5.8h5.164v-5.8Zm6.101 0H16.07v5.8h5.164v-5.8Zm-6.101-7.041H9.969v5.869h5.164v-5.87Z"
/>
</svg>
</Button>
</div>
</div>
<div className="z-10">
<HeroCards />
</div>
<div className="shadow"></div>
</section>
);
};
export default Hero;

View file

@ -0,0 +1,188 @@
"use client";
import { BorderBeam } from "@/components/ui/border-beam";
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
import { Badge } from "@/components/ui/badge";
import { Button, buttonVariants } from "@/components/ui/button";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
import Image from "next/image";
import { Happy_Monkey } from "next/font/google";
import { ArchiveRestore, Headset, Infinity, LightbulbIcon } from "lucide-react";
import Iconss from "../ui/icons";
import { useRouter } from "next/navigation";
const happyMonkey = Happy_Monkey({
preload: true,
weight: ["400"],
subsets: ["latin"],
});
const HeroCards = () => {
const router = useRouter();
const cards = {
aboutCard: {
description:
"Discover more about our services and offerings. We aim to provide the best experience for our users.",
socialLinks: {
x: "https://x.com/SVR_JS",
Mastodon: "https://mastodon.social/@svrjs",
Bluesky: "https://bsky.app/profile/svrjs.org",
Odysee: "https://odysee.com/@SVRJS",
},
},
pricingCard: {
planName: "Pro Plan",
badgeTitle: "Popular",
pricePerMonth: "$0",
description:
"Get the best features and priority support with our Pro Plan.",
primaryButtonText: "Download SVR Now",
onPrimaryButtonClick: () => router.push("/downloads"),
features: [
{
title: "Unlimited Projects",
icons: <Infinity className="rounded-full" width={25} height={25} />,
},
{
title: "Priority Support",
icons: (
<ArchiveRestore className="rounded-full" width={25} height={25} />
),
},
{
title: "Free Updates",
icons: <Headset className="rounded-full" width={25} height={25} />,
},
],
curlyText: "Best Value!",
},
serviceCard: {
title: "Our Services",
description:
"We offer a variety of services to cater to your needs, including web development, SEO, and more.",
},
};
return (
<div className="hidden lg:flex flex-row flex-wrap gap-8 relative w-[700px] h-[500px]">
{/* Twitter First Top left */}
<Card className="absolute w-[340px] -top-[15px] drop-shadow-xl shadow-black/10 dark:shadow-white/10">
<CardHeader className="flex flex-row items-center gap-4 pb-2">
<Avatar>
<AvatarImage
alt={"twitteravatar"}
src={"https://github.com/shadcn.png"}
/>
<AvatarFallback>Proxy</AvatarFallback>
</Avatar>
<div className="flex flex-col">
<CardTitle className="text-lg">Proxy</CardTitle>
<CardDescription>@proxyxd_s</CardDescription>
</div>
</CardHeader>
<CardContent>
Svrjs has the best server side rendering{" "}
<span className="text-sky-400">#SVRJSONTOP</span>
</CardContent>
<BorderBeam className="-z-10" />
</Card>
{/* Socials Second top right */}
<Card className="absolute right-[20px] top-4 w-80 flex flex-col justify-center items-center drop-shadow-xl shadow-black/10 dark:shadow-white/10">
<CardHeader className="flex justify-center items-center pb-2">
<CardTitle className="text-center">Socials</CardTitle>
<CardDescription className="font-medium text-primary"></CardDescription>
</CardHeader>
<CardContent className="text-center text-muted-foreground pb-2">
<p>{cards.aboutCard.description}</p>
</CardContent>
<CardFooter>
<Iconss />
</CardFooter>
<BorderBeam className="-z-10" />
</Card>
{/* Pricings Bottom left */}
<Card className="absolute top-[170px] left-[50px] w-72 drop-shadow-xl shadow-black/10 dark:shadow-white/10">
<CardHeader>
<CardTitle className="flex items-center justify-between">
{cards.pricingCard.planName}
<Badge variant="secondary" className="text-sm text-primary">
{cards.pricingCard.badgeTitle}
</Badge>
</CardTitle>
<div>
<span className="text-3xl font-bold">
{cards.pricingCard.pricePerMonth}
</span>
<span className="text-muted-foreground"> /month</span>
</div>
<CardDescription>{cards.pricingCard.description}</CardDescription>
<Button
size="sm"
className="w-full"
onClick={cards.pricingCard.onPrimaryButtonClick}
>
{cards.pricingCard.primaryButtonText}
</Button>
</CardHeader>
<hr className="w-4/5 m-auto -mt-2 mb-4" />
<CardFooter className="flex">
<div className="space-y-3">
{cards.pricingCard.features.map((benefit) => (
<span
key={benefit.title}
className="inline-flex justify-center items-center gap-x-3"
>
{benefit.icons}
<h3>{benefit.title}</h3>
</span>
))}
</div>
</CardFooter>
<BorderBeam className="-z-10" />
<div className="pointer-events-none dark:invert -scale-x-100 absolute w-36 top-[9.5rem] -left-[7.5rem] inline-flex justify-center items-center gap-1">
<Image
src="/curly-arrow.png"
width={35}
height={35}
alt="Curly arrow"
/>
<span
style={happyMonkey.style}
className="mt-10 font-bold text-black -scale-x-100 text-sm"
>
{cards.pricingCard.curlyText}
</span>
</div>
</Card>
{/* Service */}
<Card className="absolute w-[350px] -right-[10px] bottom-[75px] drop-shadow-xl shadow-black/10 dark:shadow-white/10">
<CardHeader className="space-y-1 flex md:flex-row justify-start items-start gap-4">
<div className="mt-1 bg-primary/20 p-1 rounded-2xl">
<LightbulbIcon className="fill-black dark:fill-[#F596D3]" />
</div>
<div>
<CardTitle>{cards.serviceCard.title}</CardTitle>
<CardDescription className="text-md mt-2">
{cards.serviceCard.description}
</CardDescription>
</div>
</CardHeader>
<BorderBeam className="-z-10" />
</Card>
</div>
);
};
export default HeroCards;

View file

@ -0,0 +1,35 @@
import { Features } from "@/constants";
import React from "react";
import { Card, CardContent, CardHeader, CardTitle } from "../ui/card";
const HowItWorks = () => {
return (
<section className="container text-center py-12 sm:py-24">
<h2 className="text-3xl md:text-5xl font-bold">
Accelerate your{" "}
<span className="bg-gradient-to-b from-green-300 to-primary text-transparent bg-clip-text">
development
</span>
</h2>
<p className="md:w-3/4 mx-auto mt-4 mb-8 text-lg md:text-xl text-muted-foreground">
Beautifully designed components that you can copy and paste into your
apps. Accessible. Customizable. Open Source.
</p>
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
{Features.map(({ icon, title, description }) => (
<Card key={title} className="bg-muted/50">
<CardHeader>
<CardTitle className="grid gap-4 place-items-center">
{icon}
{title}
</CardTitle>
</CardHeader>
<CardContent>{description}</CardContent>
</Card>
))}
</div>
</section>
);
};
export default HowItWorks;

View file

@ -0,0 +1,94 @@
"use client";
import { SVGProps } from "react";
const Logo = (props: SVGProps<SVGSVGElement>) => (
<svg
xmlns="http://www.w3.org/2000/svg"
width={194}
height={48}
viewBox="0 0 194 48"
fill="none"
{...props}
>
<path
fill="currentColor"
d="M53.22 40.315c-2.31 0-4.38-.375-6.21-1.125s-3.3-1.86-4.41-3.33c-1.08-1.47-1.65-3.24-1.71-5.31h8.19c.12 1.17.525 2.07 1.215 2.7.69.6 1.59.9 2.7.9 1.14 0 2.04-.255 2.7-.765.66-.54.99-1.275.99-2.205 0-.78-.27-1.425-.81-1.935-.51-.51-1.155-.93-1.935-1.26-.75-.33-1.83-.705-3.24-1.125-2.04-.63-3.705-1.26-4.995-1.89-1.29-.63-2.4-1.56-3.33-2.79-.93-1.23-1.395-2.835-1.395-4.815 0-2.94 1.065-5.235 3.195-6.885 2.13-1.68 4.905-2.52 8.325-2.52 3.48 0 6.285.84 8.415 2.52 2.13 1.65 3.27 3.96 3.42 6.93H56.01c-.06-1.02-.435-1.815-1.125-2.385-.69-.6-1.575-.9-2.655-.9-.93 0-1.68.255-2.25.765-.57.48-.855 1.185-.855 2.115 0 1.02.48 1.815 1.44 2.385.96.57 2.46 1.185 4.5 1.845 2.04.69 3.69 1.35 4.95 1.98 1.29.63 2.4 1.545 3.33 2.745.93 1.2 1.395 2.745 1.395 4.635 0 1.8-.465 3.435-1.395 4.905-.9 1.47-2.22 2.64-3.96 3.51-1.74.87-3.795 1.305-6.165 1.305ZM98.68 8.41 87.475 40h-9.63L66.642 8.41h8.19l7.83 23.85 7.874-23.85h8.145ZM117.557 40l-6.57-11.925h-1.845V40h-7.695V8.41h12.915c2.49 0 4.605.435 6.345 1.305 1.77.87 3.09 2.07 3.96 3.6.87 1.5 1.305 3.18 1.305 5.04 0 2.1-.6 3.975-1.8 5.625-1.17 1.65-2.91 2.82-5.22 3.51l7.29 12.51h-8.685Zm-8.415-17.37h4.77c1.41 0 2.46-.345 3.15-1.035.72-.69 1.08-1.665 1.08-2.925 0-1.2-.36-2.145-1.08-2.835-.69-.69-1.74-1.035-3.15-1.035h-4.77v7.83Zm24.81 17.73c-1.35 0-2.46-.39-3.33-1.17-.84-.81-1.26-1.8-1.26-2.97 0-1.2.42-2.205 1.26-3.015.87-.81 1.98-1.215 3.33-1.215 1.32 0 2.4.405 3.24 1.215.87.81 1.305 1.815 1.305 3.015 0 1.17-.435 2.16-1.305 2.97-.84.78-1.92 1.17-3.24 1.17Zm28.45-31.95v21.51c0 3.33-.945 5.895-2.835 7.695-1.86 1.8-4.38 2.7-7.56 2.7-3.33 0-6-.945-8.01-2.835-2.01-1.89-3.015-4.575-3.015-8.055h7.65c0 1.32.27 2.325.81 3.015.54.66 1.32.99 2.34.99.93 0 1.65-.3 2.16-.9.51-.6.765-1.47.765-2.61V8.41h7.695Zm17.196 31.905c-2.31 0-4.38-.375-6.21-1.125s-3.3-1.86-4.41-3.33c-1.08-1.47-1.65-3.24-1.71-5.31h8.19c.12 1.17.525 2.07 1.215 2.7.69.6 1.59.9 2.7.9 1.14 0 2.04-.255 2.7-.765.66-.54.99-1.275.99-2.205 0-.78-.27-1.425-.81-1.935-.51-.51-1.155-.93-1.935-1.26-.75-.33-1.83-.705-3.24-1.125-2.04-.63-3.705-1.26-4.995-1.89-1.29-.63-2.4-1.56-3.33-2.79-.93-1.23-1.395-2.835-1.395-4.815 0-2.94 1.065-5.235 3.195-6.885 2.13-1.68 4.905-2.52 8.325-2.52 3.48 0 6.285.84 8.415 2.52 2.13 1.65 3.27 3.96 3.42 6.93h-8.325c-.06-1.02-.435-1.815-1.125-2.385-.69-.6-1.575-.9-2.655-.9-.93 0-1.68.255-2.25.765-.57.48-.855 1.185-.855 2.115 0 1.02.48 1.815 1.44 2.385.96.57 2.46 1.185 4.5 1.845 2.04.69 3.69 1.35 4.95 1.98 1.29.63 2.4 1.545 3.33 2.745.93 1.2 1.395 2.745 1.395 4.635 0 1.8-.465 3.435-1.395 4.905-.9 1.47-2.22 2.64-3.96 3.51-1.74.87-3.795 1.305-6.165 1.305Z"
/>
<path
d="M42.32 18.844V7.992h14.126v21.702H42.32Z"
style={{
fill: "#fefefe",
strokeWidth: 0.0189204,
}}
transform="matrix(.57654 0 0 .57653 -3.77 -.913)"
/>
<path
d="M166.84 38.111a1.35 1.35 0 0 0-1.354 1.354v38.793c0 .75.604 1.353 1.354 1.353h12.418a1.35 1.35 0 0 0 1.353-1.353V56.916h14.467v21.342c0 .75.604 1.353 1.354 1.353h12.418a1.35 1.35 0 0 0 1.353-1.353V39.465a1.35 1.35 0 0 0-1.353-1.354h-29.592z"
style={{
fill: "#ff0",
fillOpacity: 1,
strokeWidth: 1,
}}
transform="matrix(.15254 0 0 .15255 -3.77 -.913)"
/>
<path
d="M38.707 34.451c-.714 0-1.289.575-1.289 1.29v83.343c0 .714.575 1.289 1.289 1.289h175.285c.715 0 1.29-.575 1.29-1.289V35.74c0-.714-.575-1.289-1.29-1.289zm135.363 5.91c.256.012.515.042.774.09 2.254.423 4.443 2.624 4.869 4.895.44 2.35-1.137 5.122-3.54 6.213-3.283 1.491-7.147-.328-8.206-3.864-1.098-3.665 2.264-7.508 6.103-7.334zm13.762 0c.256.012.512.042.772.09 2.254.423 4.443 2.624 4.869 4.895.44 2.35-1.138 5.122-3.54 6.213-3.284 1.491-7.147-.328-8.206-3.864-1.099-3.665 2.265-7.508 6.105-7.334zm14.08 0c.256.012.513.042.772.09 2.254.423 4.443 2.624 4.869 4.895.44 2.35-1.137 5.122-3.54 6.213-3.284 1.491-7.147-.328-8.206-3.864-1.098-3.665 2.266-7.508 6.105-7.334zM187.52 66.88c3.495 0 6.08 2.518 6.08 5.922 0 2.339-1.172 4.243-3.268 5.312-2.15 1.097-5.214.686-7.086-.953-1.002-.877-1.807-2.819-1.807-4.363 0-3.4 2.586-5.918 6.08-5.918zm-13.45.043c.256.012.515.041.774.09 2.254.423 4.443 2.623 4.869 4.894.44 2.35-1.137 5.12-3.54 6.211-3.283 1.492-7.147-.325-8.206-3.861-1.098-3.665 2.264-7.509 6.103-7.334zm27.842 0c.256.012.513.041.772.09 2.254.423 4.443 2.623 4.869 4.894.44 2.35-1.137 5.12-3.54 6.211-3.284 1.492-7.147-.325-8.206-3.861-1.098-3.665 2.266-7.509 6.105-7.334zM173.76 93.76c3.496 0 6.08 2.516 6.08 5.92 0 2.339-1.172 4.244-3.268 5.314-2.15 1.097-5.214.684-7.086-.955-1.002-.877-1.806-2.819-1.806-4.363 0-3.4 2.585-5.916 6.08-5.916zm13.76 0c3.495 0 6.08 2.516 6.08 5.92 0 2.339-1.172 4.244-3.268 5.314-2.15 1.097-5.214.684-7.086-.955-1.002-.877-1.807-2.819-1.807-4.363 0-3.4 2.586-5.916 6.08-5.916zm14.08 0c3.495 0 6.08 2.516 6.08 5.92 0 2.339-1.172 4.244-3.268 5.314-2.15 1.097-5.214.684-7.086-.955-1.002-.877-1.806-2.819-1.806-4.363 0-3.4 2.585-5.916 6.08-5.916z"
style={{
fill: "#767776",
strokeWidth: 0.32,
}}
transform="matrix(.15254 0 0 .15255 -3.77 -.913)"
/>
<path
d="M22.225 63.584c-9.433-.053-8.71-.002-10.19-.728-.65-.319-.921-.517-1.495-1.09-.574-.574-.773-.847-1.091-1.495-.758-1.543-.686 1.133-.686-25.473 0-26.606-.072-23.93.686-25.473.318-.649.517-.921 1.09-1.495.574-.573.847-.772 1.495-1.09 1.537-.755-.601-.687 21.325-.687 21.925 0 19.788-.068 21.324.686.649.319.921.518 1.495 1.091.573.574.772.846 1.09 1.495.758 1.543.686-1.133.686 25.473 0 26.606.072 23.93-.686 25.473-.318.648-.517.92-1.09 1.494-.574.574-.846.772-1.495 1.09-1.026.504-1.583.64-2.825.688-1.681.065-20.67.091-29.633.04zm32.564-34.386c.67-.314 1.129-.761 1.45-1.414.246-.5.276-.638.276-1.284 0-.645-.03-.783-.276-1.283-.321-.653-.78-1.1-1.45-1.414l-.475-.224H12.319l-.476.224a2.902 2.902 0 0 0-1.45 1.414c-.245.5-.275.638-.275 1.282 0 .6.037.8.22 1.19.39.832 1.015 1.382 1.878 1.652.292.092 4.135.11 21.227.096l20.87-.016zm0-7.112c.67-.314 1.129-.761 1.45-1.414.246-.5.276-.638.276-1.284 0-.645-.03-.783-.276-1.283-.321-.653-.78-1.1-1.45-1.414l-.475-.224H12.319l-.476.224a2.902 2.902 0 0 0-1.45 1.414c-.245.5-.275.638-.275 1.282 0 .6.037.8.22 1.19.39.832 1.015 1.382 1.878 1.652.292.092 4.135.11 21.227.096l20.87-.016zm0-7.027c.67-.314 1.129-.762 1.45-1.414.246-.5.276-.639.276-1.284s-.03-.784-.276-1.284c-.321-.652-.78-1.1-1.45-1.414l-.475-.223H12.319l-.476.223a2.91 2.91 0 0 0-1.45 1.414c-.245.5-.275.639-.275 1.282 0 .6.037.8.22 1.191.39.831 1.015 1.381 1.878 1.652.292.092 4.135.109 21.227.096l20.87-.016z"
style={{
fill: "#02f402",
strokeWidth: 0.0846667,
}}
transform="matrix(.57654 0 0 .57653 -3.77 -.913)"
/>
<path
d="M16.535 39.881a8.588 8.588 0 0 0-.417.013c-.557.03-1.098.109-1.393.224-.614.24-1.128.687-1.414 1.23-.202.384-.23.529-.23 1.24 0 .903.088 1.143.604 1.644.396.385.979.598 2.448.896 1.853.376 2.24.597 2.24 1.285 0 .772-.561 1.18-1.632 1.184-1.085.004-1.706-.323-1.932-1.015-.056-.171-.144-.34-.195-.374-.052-.034-.422-.063-.824-.064-.888-.002-.965.057-.837.631.276 1.23 1.081 1.974 2.483 2.295 1.172.268 2.833.01 3.675-.57 1.228-.844 1.52-2.847.56-3.837-.444-.456-1.039-.698-2.421-.985-1.651-.343-1.796-.387-2.09-.635-.215-.18-.258-.279-.258-.584 0-.683.523-1.056 1.481-1.056.89 0 1.464.327 1.673.954l.12.357h1.678l-.009-.474c-.02-1.09-.894-1.978-2.239-2.277-.27-.06-.659-.085-1.071-.082zm33.95 0a8.588 8.588 0 0 0-.416.013c-.557.03-1.097.109-1.392.224-.615.24-1.129.687-1.415 1.23-.202.384-.229.529-.23 1.24 0 .903.088 1.143.604 1.644.396.385.98.598 2.448.896 1.853.376 2.24.597 2.24 1.285 0 .772-.56 1.18-1.631 1.184-1.085.004-1.707-.323-1.933-1.015-.056-.171-.143-.34-.195-.374-.051-.034-.422-.063-.824-.064-.888-.002-.965.057-.836.631.276 1.23 1.08 1.974 2.482 2.295 1.172.268 2.833.01 3.676-.57 1.227-.844 1.52-2.847.559-3.837-.443-.456-1.038-.698-2.42-.985-1.652-.343-1.797-.387-2.092-.635-.214-.18-.258-.279-.258-.584 0-.683.525-1.056 1.483-1.056.89 0 1.463.327 1.672.954l.12.357h1.679l-.01-.474c-.02-1.09-.894-1.978-2.238-2.277-.27-.06-.66-.085-1.072-.082zm-5.446.081c-.657 0-.763.02-.84.164-.056.105-.088 1.368-.088 3.52v3.356l-.247.248c-.21.209-.315.247-.681.247-.645 0-.816-.205-.89-1.067a7.664 7.664 0 0 0-.091-.777c-.026-.082-.253-.103-.904-.085l-.87.025v.804c0 1.497.556 2.308 1.827 2.665.569.16 1.546.117 2.195-.094.333-.11.59-.277.903-.588.7-.696.706-.73.706-4.826 0-3.104-.015-3.495-.134-3.54-.074-.029-.473-.052-.886-.052zm-18.134.002c-.432 0-.83.03-.885.065-.055.035-.503 1.45-.995 3.145-.491 1.694-.913 3.1-.936 3.124-.024.024-.057.027-.074.008-.016-.02-.439-1.426-.94-3.125-.5-1.7-.953-3.12-1.006-3.155-.054-.035-.46-.054-.901-.042-.673.018-.808.044-.83.163-.037.188 2.774 8.522 2.925 8.673.08.08.329.117.786.117.603 0 .681-.018.787-.19.212-.345 2.957-8.52 2.906-8.654-.04-.105-.205-.13-.837-.129zm5.608.016a64.154 64.154 0 0 0-1.06.003l-2.455.022-.022 4.397c-.017 3.462.001 4.412.085 4.465.124.08 1.427.094 1.624.019.115-.045.134-.273.135-1.672 0-.891.034-1.672.073-1.735.057-.09.336-.107 1.29-.083 1.72.043 1.768.088 1.769 1.662 0 .545.04 1.132.087 1.305.145.522.25.575 1.14.575.435 0 .85-.024.924-.052.178-.068.17-.253-.023-.528-.124-.178-.17-.455-.216-1.325-.032-.606-.098-1.251-.146-1.435-.086-.323-.492-.818-.75-.913-.093-.035-.007-.155.32-.446.655-.585.815-.942.819-1.827.003-.644-.026-.782-.253-1.227a2.116 2.116 0 0 0-1.501-1.133c-.208-.043-.874-.069-1.84-.073zm-.85 1.594c.181 0 .404.004.676.01l1.372.03.247.277c.21.235.247.347.247.745 0 .55-.154.895-.463 1.036-.236.108-2.764.147-2.867.044-.083-.083-.066-1.913.019-2.048.043-.068.222-.095.768-.094zm5.844 5.495v.885c0 .629.031.897.106.927.177.072 1.538.059 1.655-.015.08-.05.102-.294.084-.91l-.025-.839-.91-.024z"
style={{
fill: "#000",
strokeWidth: 0.0846667,
}}
transform="matrix(.57654 0 0 .57653 -3.77 -.913)"
/>
<path
d="M115.715 236.383a.602.602 0 0 0-.604.603v12.047l.014 1.498.086 9.03-.1.125v13.777h-62.84l-2.095.992a12.68 12.68 0 0 0-6.12 6.121c-.919 1.94-1.017 2.432-1.017 5.184 0 2.751.098 3.243 1.018 5.183a12.68 12.68 0 0 0 6.119 6.121l2.144 1.016 31.026.08 5.91.016h77.473l5.605-.016 29.426-.082 2.144-1.015a12.676 12.676 0 0 0 6.12-6.12c.919-1.94 1.015-2.432 1.015-5.183s-.096-3.245-1.016-5.186a12.676 12.676 0 0 0-6.119-6.119l-2.095-.992h-60.313v-36.477a.602.602 0 0 0-.603-.603z"
style={{
fill: "#999998",
strokeWidth: 0.32,
}}
transform="matrix(.15254 0 0 .15255 -3.77 -.913)"
/>
<path
d="M42.415 280.972c-.581-.136-1.244-.507-1.76-.986-.558-.516-.824-.918-1.114-1.68-.184-.485-.19-.592-.186-3.422.005-3.115.04-3.468.427-4.285.26-.552 1.02-1.398 1.514-1.687.833-.488 1.247-.575 2.753-.575s1.92.087 2.753.575c.494.29 1.253 1.135 1.514 1.687.387.817.422 1.17.427 4.285.004 2.83-.002 2.937-.186 3.423-.29.761-.556 1.163-1.114 1.68-.533.493-1.171.845-1.795.988-.452.105-2.787.102-3.233-.003z"
style={{
fill: "#fd7f00",
strokeWidth: 0.0846667,
}}
transform="matrix(.57654 0 0 .57653 -9.65 -116.597)"
/>
<path
d="M33.359 6.053c-21.926 0-19.788-.069-21.325.686-.648.319-.92.517-1.494 1.091s-.772.846-1.09 1.495c-.759 1.543-.687-1.133-.687 25.473 0 26.606-.072 23.93.686 25.473.319.648.517.92 1.09 1.494.575.574.846.772 1.495 1.09 1.48.727.758.676 10.191.729 8.964.05 27.952.024 29.633-.041 1.242-.048 1.799-.184 2.825-.687.649-.319.92-.517 1.495-1.09.573-.574.772-.847 1.09-1.495.758-1.543.686 1.133.686-25.473 0-26.606.072-23.93-.686-25.473-.318-.649-.517-.921-1.09-1.495-.574-.573-.847-.772-1.495-1.09-1.537-.755.602-.687-21.324-.687ZM12.319 9.44h41.995l.476.223a2.914 2.914 0 0 1 1.45 1.414c.245.5.275.639.275 1.284s-.03.784-.276 1.284c-.321.652-.78 1.1-1.45 1.414l-.475.223-20.87.016c-17.093.013-20.935-.005-21.228-.096-.863-.27-1.488-.82-1.878-1.652-.183-.39-.22-.59-.22-1.19 0-.644.03-.783.276-1.283.321-.652.78-1.1 1.45-1.414zm0 7.027h41.995l.476.224c.669.313 1.128.76 1.45 1.413.245.5.275.64.275 1.285 0 .645-.03.783-.276 1.283-.321.653-.78 1.1-1.45 1.414l-.475.223-20.87.016c-17.093.013-20.935-.005-21.228-.096-.863-.27-1.488-.82-1.878-1.651-.183-.391-.22-.591-.22-1.192 0-.643.03-.782.276-1.282a2.9 2.9 0 0 1 1.45-1.413zm0 7.113h41.995l.476.223c.669.314 1.128.761 1.45 1.414.245.5.275.638.275 1.283 0 .646-.03.784-.276 1.284-.321.653-.78 1.1-1.45 1.414l-.475.224-20.87.015c-17.093.013-20.935-.004-21.228-.096-.863-.27-1.488-.82-1.878-1.651-.183-.39-.22-.591-.22-1.191 0-.644.03-.783.276-1.282.321-.653.78-1.1 1.45-1.414zm20.955 8.833c6.13 0 12.259.046 12.83.139a12.478 12.478 0 0 1 6.152 2.89c2.046 1.798 3.473 4.41 3.923 7.185.165 1.02.165 3.726 0 4.746-.45 2.775-1.877 5.387-3.923 7.185a12.477 12.477 0 0 1-6.153 2.89c-1.089.177-24.692.17-25.74-.007-2.202-.372-4.402-1.417-6.071-2.883-2.046-1.798-3.473-4.41-3.922-7.185-.166-1.02-.166-3.726 0-4.746a12.476 12.476 0 0 1 2.89-6.153c1.797-2.046 4.41-3.473 7.184-3.923.571-.092 6.7-.138 12.83-.138zm-10.278 4.02c.062-.009.125 0 .188 0-.063 0-.126-.009-.188 0z"
style={{
fill: "#017801",
strokeWidth: 0.0846667,
}}
transform="matrix(.57654 0 0 .57653 -3.77 -.913)"
/>
<path
d="M16.417 9.252c-.072.018-.142.04-.213.061h.12l.105-.034zm-5.403.16c.132.019.265.028.398.026v-.003a14.897 14.897 0 0 1-.398-.023Zm4.417 1.033-.233.027-.233.027-.023 1.714-.023 1.715h.512v-1.525h1.778v1.525h.422v-3.472h-.422v1.524H15.43v-.767zm2.625.011v.423h1.1v3.049h.508v-3.049h1.101v-.423H19.41Zm2.878 0v.423h1.1v3.049h.509v-3.049h1.1v-.423H22.29Zm3.217 0v3.472h.424v-1.44h1.286l.276-.13.276-.131.116-.217.116-.218v-.64l-.116-.217-.116-.217-.276-.131-.276-.13h-.854zm.424.423h1.24l.184.197.184.196v.442l-.222.176-.224.175h-1.162v-.593zm-9.144 6.593-.233.027-.233.027-.023 1.715-.023 1.714h.512V19.43h1.778v1.524h.422v-3.471h-.422v1.524H15.43v-.768zm2.625.012v.423h1.1v3.048h.508v-3.048h1.101v-.423H19.41Zm2.878 0v.423h1.1v3.048h.509v-3.048h1.1v-.423H22.29Zm3.217 0v3.471h.424v-1.44h1.286l.276-.13.276-.13.116-.218.116-.217v-.64l-.116-.218-.116-.217-.276-.13-.276-.131h-.854zm.424.423h1.24l.184.196.184.196v.442l-.222.176-.224.175h-1.162V18.5Zm-9.144 6.677-.233.027-.233.027-.023 1.714-.023 1.715h.512v-1.524h1.778v1.524h.422v-3.472h-.422v1.525H15.43v-.768zm2.625.011v.424h1.1v3.048h.508v-3.048h1.101v-.424H19.41Zm2.878 0v.424h1.1v3.048h.509v-3.048h1.1v-.424H22.29Zm3.217 0v3.472h.424v-1.44h1.286l.276-.13.276-.131.116-.217.116-.217v-.64l-.116-.218-.116-.217-.276-.13-.276-.132h-.854zm.424.424h1.24l.184.196.184.196v.442l-.222.176-.224.175h-1.162v-.592z"
style={{
fill: "#666",
fillOpacity: 1,
strokeWidth: 0.0846667,
}}
transform="matrix(.57654 0 0 .57653 -3.77 -.913)"
/>
</svg>
);
export default Logo;

View file

@ -0,0 +1,76 @@
import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
SheetTrigger,
} from "../ui/sheet";
import { Menu } from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import ThemeToggle from "../ui/theme-toggle";
import { NAVBAR } from "@/constants";
import { buttonVariants } from "../ui/button";
import Logo from "./Logo";
const MobileNav = () => {
return (
<div className="flex md:hidden">
<ThemeToggle />
<Sheet>
<SheetTrigger>
<span className="sr-only">Menu</span>
<Menu className="w-5 h-5" />
</SheetTrigger>
<SheetContent>
<SheetHeader>
<SheetTitle>
<Logo width={120} height={40} />
</SheetTitle>
</SheetHeader>
<nav
id="mobile-menu"
className="flex flex-col justify-center items-center gap-2 mt-4"
>
{NAVBAR.centerLinks?.map(({ href = "", label, target }) => (
<Link
key={label}
href={href}
target={target}
className={buttonVariants({ variant: "ghost" })}
>
{label}
</Link>
))}
{NAVBAR.rightLinks?.map(({ href, label, target }) => (
<Link
key={label}
href={href}
target={target}
className={`w-[110px] gap-2 ${buttonVariants({
variant: "secondary",
})}`}
>
<svg
xmlns="http://www.w3.org/2000/svg"
width={25}
height={25}
viewBox="0 0 123 123"
fill="none"
>
<path
fill="currentColor"
d="M120.208 55.953 66.715 2.463a7.885 7.885 0 0 0-11.158 0l-11.109 11.11 14.088 14.088a9.373 9.373 0 0 1 11.87 11.948l13.578 13.579a9.368 9.368 0 0 1 9.704 2.23 9.386 9.386 0 0 1-6.64 16.025 9.393 9.393 0 0 1-9.21-7.547 9.384 9.384 0 0 1 .526-5.416L65.697 45.817v33.33a9.385 9.385 0 0 1 2.48 15.053 9.386 9.386 0 0 1-15.311-3.046A9.388 9.388 0 0 1 54.9 80.923a9.378 9.378 0 0 1 3.078-2.052V45.235a9.336 9.336 0 0 1-3.078-2.047A9.4 9.4 0 0 1 52.88 32.92l-13.89-13.89L2.311 55.703a7.89 7.89 0 0 0 0 11.16l53.495 53.497a7.895 7.895 0 0 0 11.157 0l53.244-53.245a7.9 7.9 0 0 0 0-11.162Z"
/>
</svg>
{label}
</Link>
))}
</nav>
</SheetContent>
</Sheet>
</div>
);
};
export default MobileNav;

View file

@ -0,0 +1,90 @@
"use client";
import {
NavigationMenu,
NavigationMenuItem,
NavigationMenuList,
} from "@radix-ui/react-navigation-menu";
import Image from "next/image";
import Link from "next/link";
import ThemeToggle from "../ui/theme-toggle";
import { NAVBAR } from "@/constants";
import { buttonVariants } from "../ui/button";
import MobileNav from "./MobileNav";
import { usePathname } from "next/navigation";
import Logo from "./Logo";
const Navbar = () => {
const pathname = usePathname();
return (
<header className="sticky border-b top-0 z-40 w-full shadow-md bg-white dark:border-b-slate-800 dark:bg-background">
{/* LOGO LEFT NAVBAR */}
<NavigationMenu className="mx-auto">
<NavigationMenuList className="container h-16 px-4 w-full flex justify-between items-center">
<NavigationMenuItem className="font-bold flex items-center">
<Link href="/#" className="inline-flex items-center gap-2">
<span className="sr-only">SVR.JS</span>
<Logo width={120} height={40} />
</Link>
</NavigationMenuItem>
{/* Mobile view */}
<NavigationMenuItem className="flex md:hidden">
<MobileNav />
</NavigationMenuItem>
{/* Desktop Menu */}
<NavigationMenuItem className="hidden md:flex">
<nav className="hidden md:flex gap-4">
{NAVBAR.centerLinks?.map(({ href, label, target }) => (
<Link
key={label}
href={href}
target={target}
className={`text-[17px] tracking-tight ${
pathname == href ? "bg-muted-foreground/20" : ""
} ${buttonVariants({
variant: "ghost",
})}`}
>
{label}
</Link>
))}
</nav>
</NavigationMenuItem>
<NavigationMenuItem className="hidden md:flex gap-2 items-center">
{NAVBAR.rightLinks?.map(({ href = "", label, target }) => (
<Link
key={label}
href={href}
target={target}
className={`border ${buttonVariants({
variant: "ghost",
size: "icon",
})}`}
>
<span className="sr-only">Git</span>
<svg
xmlns="http://www.w3.org/2000/svg"
width={25}
height={25}
viewBox="0 0 123 123"
fill="none"
>
<path
fill="currentColor"
d="M120.208 55.953 66.715 2.463a7.885 7.885 0 0 0-11.158 0l-11.109 11.11 14.088 14.088a9.373 9.373 0 0 1 11.87 11.948l13.578 13.579a9.368 9.368 0 0 1 9.704 2.23 9.386 9.386 0 0 1-6.64 16.025 9.393 9.393 0 0 1-9.21-7.547 9.384 9.384 0 0 1 .526-5.416L65.697 45.817v33.33a9.385 9.385 0 0 1 2.48 15.053 9.386 9.386 0 0 1-15.311-3.046A9.388 9.388 0 0 1 54.9 80.923a9.378 9.378 0 0 1 3.078-2.052V45.235a9.336 9.336 0 0 1-3.078-2.047A9.4 9.4 0 0 1 52.88 32.92l-13.89-13.89L2.311 55.703a7.89 7.89 0 0 0 0 11.16l53.495 53.497a7.895 7.895 0 0 0 11.157 0l53.244-53.245a7.9 7.9 0 0 0 0-11.162Z"
/>
</svg>
</Link>
))}
<ThemeToggle />
</NavigationMenuItem>
</NavigationMenuList>
</NavigationMenu>
</header>
);
};
export default Navbar;

View file

@ -0,0 +1,77 @@
"use client";
import { useState } from "react";
import { Button } from "../ui/button";
import { Input } from "../ui/input";
import Image from "next/image";
import { Happy_Monkey } from "next/font/google";
const happyMonkey = Happy_Monkey({
preload: true,
weight: "400",
subsets: ["latin"],
});
const Newsletter = () => {
const [submission, setSubmission] = useState<
"idle" | "loading" | "success" | "error"
>("idle");
const handleSubmit = async () => {
console.log("Done");
};
return (
<section id="newsletter">
<hr className="w-11/12 mx-auto" />
<div className="container py-24 md:py-32">
<h3 className="text-center text-4xl md:text-5xl md:pb-2 text-black font-bold dark:bg-clip-text dark:text-transparent dark:bg-gradient-to-b dark:from-white dark:to-neutral-400">
Join The Newsletter!
</h3>
<p className="text-lg text-muted-foreground text-center mt-4 md:mt-2 mb-8">
Choosing the right website deployment option is important when
creating a website, because it directly impacts the user experience
and the resources required to run your website.
</p>
<form
className="relative flex flex-col w-full md:flex-row md:w-6/12 lg:w-4/12 mx-auto gap-4 md:gap-2"
aria-label="Email Information"
onSubmit={handleSubmit}
>
<Input placeholder="example@subscribe.com"></Input>
<Button disabled={submission === "loading"}>Subscribe</Button>
<div className="pointer-events-none dark:invert -scale-x-100 absolute -bottom-14 right-1/2 md:right-14 inline-flex justify-center items-center gap-1">
<Image
src="/curly-arrow.png"
alt="see here"
width={35}
height={35}
/>
<span
className={`mt-10 font-bold text-black -scale-x-100 text-[15px] ${happyMonkey.className}`}
>
{submission === "idle" && "Subscribe Now"}
{submission === "loading" && (
<p className="text-sm text-center">Subscribing...</p>
)}
{submission === "success" && (
<p className="dark:invert text-sm text-center text-green-500">
🎉 Subscribed successfully...
</p>
)}
{submission === "error" && (
<p className="dark:invert text-sm text-center text-red-500">
😥 Something went wrong...
</p>
)}
</span>
</div>
</form>
</div>
<hr className="w-11/12 mx-auto" />
</section>
);
};
export default Newsletter;

View file

@ -0,0 +1,49 @@
"use client";
import React from "react";
import { Button } from "../ui/button";
import { ArrowUpRight } from "lucide-react";
import Link from "next/link";
import { useRouter } from "next/navigation";
const Partners = () => {
const router = useRouter();
const handleClick = () => {
router.push("/docs");
};
return (
<section
id="partners"
className="wrapper container py-24 md:py-28 gap-4 flex flex-col"
>
<h2 className="text-3xl md:text-5xl font-bold text-start">
<span className="bg-gradient-to-b from-green-300 to-primary text-transparent bg-clip-text">
SVRJS
</span>{" "}
in action
</h2>
<div className="w-full flex-start flex-row">
<div className="flex max-md:flex-col items-center justify-start gap-4">
<h2 className="text-md font-medium bg-accent/60 px-2 py-2 rounded-md">
Process of setting up a WordPress website running on SVR.JS.
</h2>
<Button
onClick={handleClick}
className="flex-center font-bold max-md:w-full max-w-xl"
>
Docs <ArrowUpRight />
</Button>
</div>
</div>
<video
src="/svgaction.mp4"
className="rounded-xl aspect-video bg-[#09090b]"
controls
poster="/poster.svg"
></video>
<hr className="w-full h-1" />
</section>
);
};
export default Partners;

View file

View file

@ -0,0 +1,21 @@
import { stats } from "@/constants";
import NumberTicker from "../widgets/num-tick";
const Statistics = () => {
return (
<section>
<div className="grid grid-cols-2 lg:grid-cols-4 gap-8">
{stats.map(({ title, count }) => (
<div key={title} className="space-y-2 text-center">
<h2 className="text-3xl sm:text-4xl font-bold">
<NumberTicker value={count} />
</h2>
<p className="text-xl text-muted-foreground">{title}</p>
</div>
))}
</div>
</section>
);
};
export default Statistics;

View file

@ -0,0 +1,105 @@
import React from "react";
import TestimonialCard from "../cards/testimonialCard";
const testimonials = [
{
name: "John Doe",
role: "CEO, Example Corp.",
avatar: "avatar1",
testimonial:
"Working with this team was a fantastic experience. They developed our website exactly to our specifications, and everything was seamless and well-integrated.",
rating: 5,
},
{
name: "Jane Smith",
role: "CEO, CleanCo",
avatar: "avatar2",
testimonial:
"We're thrilled with the website. It's simple, clean, and has significantly boosted our sales. The developers did an excellent job.",
rating: 4,
},
{
name: "Sam Green",
role: "Web Developer",
avatar: "avatar3",
testimonial:
"Collaborating with this team to build a SaaS-integrated website was a perfect experience. I look forward to working with them again.",
rating: 5,
},
{
name: "Chris Brown",
role: "Web Coder",
avatar: "avatar4",
testimonial:
"The team's understanding of our needs and their ability to provide fitting solutions was impressive. Their support and guidance were invaluable.",
rating: 4,
},
{
name: "Alex Johnson",
avatar: "avatar5",
testimonial:
"Exceptional service and outstanding results. They consistently deliver on time and within budget, making them our go-to partner for all our projects.",
rating: 5,
},
{
name: "Patricia Taylor",
role: "Web Developer",
avatar: "avatar6",
testimonial:
"It was great to work with them. I needed a design for a SaaS project, and it was delivered within 2 days.",
rating: 4,
},
{
name: "Emily Davis",
role: "UX Designer, Creative Agency",
avatar: "avatar7",
testimonial:
"Collaborating with them has been a pleasure. Their creativity and user-centric approach have significantly enhanced our product's usability.",
rating: 5,
},
{
name: "Michael Lee",
avatar: "avatar8",
testimonial:
"They have a keen understanding of our business needs and consistently deliver top-notch solutions. Their reliability and efficiency are commendable.",
rating: 5,
},
{
name: "Sarah Wilson",
avatar: "avatar9",
testimonial:
"Their dedication to client satisfaction is evident in everything they do. We've seen remarkable improvements in our processes thanks to their expertise.",
rating: 4,
},
];
const Testimonials = () => {
return (
<section className="mx-auto flex w-full max-w-7xl flex-col pt-12 md:pt-24">
<div className="flex flex-row items-center justify-center space-x-1">
<span className="text-white/50 text-xs lg:text-base">Testimonials</span>
</div>
<h1 className="text-3xl md:text-5xl font-bold text-center">
Hear it from{" "}
<span className="bg-gradient-to-b from-green-300 to-primary text-transparent bg-clip-text">
our users
</span>
</h1>
<ul className="wrapper columns-1 gap-5 md:columns-2 lg:columns-3 py-6 mt-6">
{testimonials.map((testimonial, idx) => (
<TestimonialCard
avatar={testimonial.avatar}
name={testimonial.name}
role={testimonial.role}
testimonial={testimonial.testimonial}
rating={testimonial.rating}
key={idx}
/>
))}
</ul>
</section>
);
};
export default Testimonials;

View file

@ -0,0 +1,10 @@
"use client";
import { SessionProvider } from "next-auth/react";
export default function AuthProvider({
children,
}: {
children: React.ReactNode;
}) {
return <SessionProvider>{children}</SessionProvider>;
}

View file

@ -0,0 +1,17 @@
import React from "react";
interface ChangelogLayoutProps {
children: React.ReactNode;
}
export const ChangelogLayout: React.FC<ChangelogLayoutProps> = ({
children,
}) => {
return (
<div className="wrapper container py-24 md:py-28 gap-4 flex flex-col">
<div className="prose max-w-full prose-lg dark:prose-invert">
{children}
</div>
</div>
);
};

View file

@ -0,0 +1,9 @@
"use client"
import * as React from "react"
import { ThemeProvider as NextThemesProvider } from "next-themes"
import { type ThemeProviderProps } from "next-themes/dist/types"
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
}

View file

@ -0,0 +1,58 @@
"use client";
import * as React from "react";
import * as AccordionPrimitive from "@radix-ui/react-accordion";
import { ChevronDown, PlusCircleIcon, PlusIcon } from "lucide-react";
import { cn } from "@/lib/utils";
const Accordion = AccordionPrimitive.Root;
const AccordionItem = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Item>
>(({ className, ...props }, ref) => (
<AccordionPrimitive.Item
ref={ref}
className={cn("border-b", className)}
{...props}
/>
));
AccordionItem.displayName = "AccordionItem";
const AccordionTrigger = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Header className="flex">
<AccordionPrimitive.Trigger
ref={ref}
className={cn(
"flex flex-1 items-center justify-between py-4 font-medium transition-all hover:underline [&[data-state=open]>svg]:rotate-45",
className
)}
{...props}
>
{children}
<PlusIcon className="h-5 w-5 shrink-0 transition-transform duration-200" />
</AccordionPrimitive.Trigger>
</AccordionPrimitive.Header>
));
AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName;
const AccordionContent = React.forwardRef<
React.ElementRef<typeof AccordionPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof AccordionPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<AccordionPrimitive.Content
ref={ref}
className="overflow-hidden text-sm transition-all data-[state=closed]:animate-accordion-up data-[state=open]:animate-accordion-down"
{...props}
>
<div className={cn("pb-4 pt-0", className)}>{children}</div>
</AccordionPrimitive.Content>
));
AccordionContent.displayName = AccordionPrimitive.Content.displayName;
export { Accordion, AccordionItem, AccordionTrigger, AccordionContent };

View file

@ -0,0 +1,25 @@
import { cn } from "@/lib/utils";
import { ReactNode } from "react";
export default function AnimatedGradientText({
children,
className,
}: {
children: ReactNode;
className?: string;
}) {
return (
<div
className={cn(
"group relative mx-auto flex max-w-fit flex-row items-center justify-center rounded-2xl bg-white/40 px-4 py-1.5 text-sm font-medium shadow-[inset_0_-8px_10px_#8fdfff1f] backdrop-blur-sm transition-shadow duration-500 ease-out [--bg-size:300%] hover:shadow-[inset_0_-5px_10px_#8fdfff3f] dark:bg-black/40",
className,
)}
>
<div
className={`direction-reverse absolute inset-0 block h-full w-full animate-gradient bg-gradient-to-r from-[#26751a] to-[#154300] dark:bg-gradient-to-r dark:from-[#aafc9e]/80 dark:to-[#97fd67]/30 bg-[length:var(--bg-size)_100%] p-[1px] ![mask-composite:subtract] [border-radius:inherit] [mask:linear-gradient(#fff_0_0)_content-box,linear-gradient(#fff_0_0)]`}
/>
{children}
</div>
);
}

View file

@ -0,0 +1,39 @@
import { cn } from "@/lib/utils";
import { CSSProperties, FC, ReactNode } from "react";
interface AnimatedShinyTextProps {
children: ReactNode;
className?: string;
shimmerWidth?: number;
}
const AnimatedShinyText: FC<AnimatedShinyTextProps> = ({
children,
className,
shimmerWidth = 100,
}) => {
return (
<p
style={
{
"--shimmer-width": `${shimmerWidth}px`,
} as CSSProperties
}
className={cn(
"mx-auto max-w-md text-neutral-600/50 dark:text-neutral-400/50 ",
// Shimmer effect
"animate-shimmer bg-clip-text bg-no-repeat [background-position:0_0] [background-size:var(--shimmer-width)_100%] [transition:background-position_1s_cubic-bezier(.6,.6,0,1)_infinite]",
// Shimmer gradient
"bg-gradient-to-r from-transparent via-black/80 via-50% to-transparent dark:via-white/80",
className,
)}
>
{children}
</p>
);
};
export default AnimatedShinyText;

50
components/ui/avatar.tsx Normal file
View file

@ -0,0 +1,50 @@
"use client";
import * as React from "react";
import * as AvatarPrimitive from "@radix-ui/react-avatar";
import { cn } from "@/lib/utils";
const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={cn(
"relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full",
className,
)}
{...props}
/>
));
Avatar.displayName = AvatarPrimitive.Root.displayName;
const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn("aspect-square h-full w-full", className)}
{...props}
/>
));
AvatarImage.displayName = AvatarPrimitive.Image.displayName;
const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
"flex h-full w-full items-center justify-center rounded-full bg-muted",
className,
)}
{...props}
/>
));
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName;
export { Avatar, AvatarImage, AvatarFallback };

36
components/ui/badge.tsx Normal file
View file

@ -0,0 +1,36 @@
import * as React from "react";
import { cva, type VariantProps } from "class-variance-authority";
import { cn } from "@/lib/utils";
const badgeVariants = cva(
"inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
{
variants: {
variant: {
default:
"border-transparent bg-primary text-primary-foreground hover:bg-primary/80",
secondary:
"border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
destructive:
"border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80",
outline: "text-foreground",
},
},
defaultVariants: {
variant: "default",
},
},
);
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
);
}
export { Badge, badgeVariants };

View file

@ -0,0 +1,49 @@
import { cn } from "@/lib/utils";
interface BorderBeamProps {
className?: string;
size?: number;
duration?: number;
borderWidth?: number;
anchor?: number;
colorFrom?: string;
colorTo?: string;
delay?: number;
}
export const BorderBeam = ({
className,
size = 200,
duration = 15,
anchor = 90,
borderWidth = 1.5,
colorFrom = "#8803AF",
colorTo = "#61DAFB",
delay = 0,
}: BorderBeamProps) => {
return (
<div
style={
{
"--size": size,
"--duration": duration,
"--anchor": anchor,
"--border-width": borderWidth,
"--color-from": colorFrom,
"--color-to": colorTo,
"--delay": `-${delay}s`,
} as React.CSSProperties
}
className={cn(
"absolute inset-[0] rounded-[inherit] [border:calc(var(--border-width)*1px)_solid_transparent] opacity-10",
// mask styles
"![mask-clip:padding-box,border-box] ![mask-composite:intersect] [mask:linear-gradient(transparent,transparent),linear-gradient(white,white)]",
// pseudo styles
"after:absolute after:aspect-square after:w-[calc(var(--size)*1px)] after:animate-border-beam after:[animation-delay:var(--delay)] after:[background:linear-gradient(to_left,var(--color-from),var(--color-to),transparent)] after:[offset-anchor:calc(var(--anchor)*1%)_50%] after:[offset-path:rect(0_auto_auto_0_round_calc(var(--size)*1px))]",
className
)}
/>
);
};

56
components/ui/button.tsx Normal file
View file

@ -0,0 +1,56 @@
import * as React from "react"
import { Slot } from "@radix-ui/react-slot"
import { cva, type VariantProps } from "class-variance-authority"
import { cn } from "@/lib/utils"
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
{
variants: {
variant: {
default: "bg-primary text-primary-foreground hover:bg-primary/90",
destructive:
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
outline:
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
secondary:
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
ghost: "hover:bg-accent hover:text-accent-foreground",
link: "text-primary underline-offset-4 hover:underline",
},
size: {
default: "h-10 px-4 py-2",
sm: "h-9 rounded-md px-3",
lg: "h-11 rounded-md px-8",
icon: "h-10 w-10",
},
},
defaultVariants: {
variant: "default",
size: "default",
},
}
)
export interface ButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
VariantProps<typeof buttonVariants> {
asChild?: boolean
}
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button"
return (
<Comp
className={cn(buttonVariants({ variant, size, className }))}
ref={ref}
{...props}
/>
)
}
)
Button.displayName = "Button"
export { Button, buttonVariants }

86
components/ui/card.tsx Normal file
View file

@ -0,0 +1,86 @@
import * as React from "react";
import { cn } from "@/lib/utils";
const Card = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn(
"rounded-lg border bg-card text-card-foreground shadow-sm",
className,
)}
{...props}
/>
));
Card.displayName = "Card";
const CardHeader = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex flex-col space-y-1.5 p-6", className)}
{...props}
/>
));
CardHeader.displayName = "CardHeader";
const CardTitle = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLHeadingElement>
>(({ className, ...props }, ref) => (
<h3
ref={ref}
className={cn(
"text-2xl font-semibold leading-none tracking-tight",
className,
)}
{...props}
/>
));
CardTitle.displayName = "CardTitle";
const CardDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes<HTMLParagraphElement>
>(({ className, ...props }, ref) => (
<p
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
));
CardDescription.displayName = "CardDescription";
const CardContent = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
));
CardContent.displayName = "CardContent";
const CardFooter = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes<HTMLDivElement>
>(({ className, ...props }, ref) => (
<div
ref={ref}
className={cn("flex items-center p-6 pt-0", className)}
{...props}
/>
));
CardFooter.displayName = "CardFooter";
export {
Card,
CardHeader,
CardFooter,
CardTitle,
CardDescription,
CardContent,
};

122
components/ui/dialog.tsx Normal file
View file

@ -0,0 +1,122 @@
"use client"
import * as React from "react"
import * as DialogPrimitive from "@radix-ui/react-dialog"
import { X } from "lucide-react"
import { cn } from "@/lib/utils"
const Dialog = DialogPrimitive.Root
const DialogTrigger = DialogPrimitive.Trigger
const DialogPortal = DialogPrimitive.Portal
const DialogClose = DialogPrimitive.Close
const DialogOverlay = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Overlay
ref={ref}
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
/>
))
DialogOverlay.displayName = DialogPrimitive.Overlay.displayName
const DialogContent = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Content>
>(({ className, children, ...props }, ref) => (
<DialogPortal>
<DialogOverlay />
<DialogPrimitive.Content
ref={ref}
className={cn(
"fixed left-[50%] top-[50%] z-50 grid w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 border bg-background p-6 shadow-lg duration-200 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[state=closed]:slide-out-to-left-1/2 data-[state=closed]:slide-out-to-top-[48%] data-[state=open]:slide-in-from-left-1/2 data-[state=open]:slide-in-from-top-[48%] sm:rounded-lg",
className
)}
{...props}
>
{children}
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
</DialogPrimitive.Content>
</DialogPortal>
))
DialogContent.displayName = DialogPrimitive.Content.displayName
const DialogHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-1.5 text-center sm:text-left",
className
)}
{...props}
/>
)
DialogHeader.displayName = "DialogHeader"
const DialogFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
)
DialogFooter.displayName = "DialogFooter"
const DialogTitle = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Title>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Title
ref={ref}
className={cn(
"text-lg font-semibold leading-none tracking-tight",
className
)}
{...props}
/>
))
DialogTitle.displayName = DialogPrimitive.Title.displayName
const DialogDescription = React.forwardRef<
React.ElementRef<typeof DialogPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof DialogPrimitive.Description>
>(({ className, ...props }, ref) => (
<DialogPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
))
DialogDescription.displayName = DialogPrimitive.Description.displayName
export {
Dialog,
DialogPortal,
DialogOverlay,
DialogClose,
DialogTrigger,
DialogContent,
DialogHeader,
DialogFooter,
DialogTitle,
DialogDescription,
}

View file

@ -0,0 +1,200 @@
"use client"
import * as React from "react"
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { Check, ChevronRight, Circle } from "lucide-react"
import { cn } from "@/lib/utils"
const DropdownMenu = DropdownMenuPrimitive.Root
const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger
const DropdownMenuGroup = DropdownMenuPrimitive.Group
const DropdownMenuPortal = DropdownMenuPrimitive.Portal
const DropdownMenuSub = DropdownMenuPrimitive.Sub
const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup
const DropdownMenuSubTrigger = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubTrigger>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean
}
>(({ className, inset, children, ...props }, ref) => (
<DropdownMenuPrimitive.SubTrigger
ref={ref}
className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent",
inset && "pl-8",
className
)}
{...props}
>
{children}
<ChevronRight className="ml-auto h-4 w-4" />
</DropdownMenuPrimitive.SubTrigger>
))
DropdownMenuSubTrigger.displayName =
DropdownMenuPrimitive.SubTrigger.displayName
const DropdownMenuSubContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.SubContent>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.SubContent>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.SubContent
ref={ref}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
))
DropdownMenuSubContent.displayName =
DropdownMenuPrimitive.SubContent.displayName
const DropdownMenuContent = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Content>
>(({ className, sideOffset = 4, ...props }, ref) => (
<DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content
ref={ref}
sideOffset={sideOffset}
className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
className
)}
{...props}
/>
</DropdownMenuPrimitive.Portal>
))
DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName
const DropdownMenuItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Item>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Item
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName
const DropdownMenuCheckboxItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.CheckboxItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.CheckboxItem>
>(({ className, children, checked, ...props }, ref) => (
<DropdownMenuPrimitive.CheckboxItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
checked={checked}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Check className="h-4 w-4" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.CheckboxItem>
))
DropdownMenuCheckboxItem.displayName =
DropdownMenuPrimitive.CheckboxItem.displayName
const DropdownMenuRadioItem = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.RadioItem>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.RadioItem>
>(({ className, children, ...props }, ref) => (
<DropdownMenuPrimitive.RadioItem
ref={ref}
className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
)}
{...props}
>
<span className="absolute left-2 flex h-3.5 w-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator>
<Circle className="h-2 w-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator>
</span>
{children}
</DropdownMenuPrimitive.RadioItem>
))
DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName
const DropdownMenuLabel = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Label>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean
}
>(({ className, inset, ...props }, ref) => (
<DropdownMenuPrimitive.Label
ref={ref}
className={cn(
"px-2 py-1.5 text-sm font-semibold",
inset && "pl-8",
className
)}
{...props}
/>
))
DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName
const DropdownMenuSeparator = React.forwardRef<
React.ElementRef<typeof DropdownMenuPrimitive.Separator>,
React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Separator>
>(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator
ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName
const DropdownMenuShortcut = ({
className,
...props
}: React.HTMLAttributes<HTMLSpanElement>) => {
return (
<span
className={cn("ml-auto text-xs tracking-widest opacity-60", className)}
{...props}
/>
)
}
DropdownMenuShortcut.displayName = "DropdownMenuShortcut"
export {
DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem,
DropdownMenuRadioItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub,
DropdownMenuSubContent,
DropdownMenuSubTrigger,
DropdownMenuRadioGroup,
}

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,
}

View file

@ -0,0 +1,71 @@
import { cn } from "@/lib/utils";
import { useId } from "react";
interface GridPatternProps {
width?: any;
height?: any;
x?: any;
y?: any;
squares?: Array<[x: number, y: number]>;
strokeDasharray?: any;
className?: string;
[key: string]: any;
}
export function GridPattern({
width = 40,
height = 40,
x = -1,
y = -1,
strokeDasharray = 0,
squares,
className,
...props
}: GridPatternProps) {
const id = useId();
return (
<svg
aria-hidden="true"
className={cn(
"pointer-events-none absolute inset-0 h-full w-full fill-gray-400/35 stroke-gray-400/35",
className,
)}
{...props}
>
<defs>
<pattern
id={id}
width={width}
height={height}
patternUnits="userSpaceOnUse"
x={x}
y={y}
>
<path
d={`M.5 ${height}V.5H${width}`}
fill="none"
strokeDasharray={strokeDasharray}
/>
</pattern>
</defs>
<rect width="100%" height="100%" strokeWidth={0} fill={`url(#${id})`} />
{squares && (
<svg x={x} y={y} className="overflow-visible">
{squares.map(([x, y]) => (
<rect
strokeWidth="0"
key={`${x}-${y}`}
width={width - 1}
height={height - 1}
x={x * width + 1}
y={y * height + 1}
/>
))}
</svg>
)}
</svg>
);
}
export default GridPattern;

117
components/ui/icons.tsx Normal file
View file

@ -0,0 +1,117 @@
import Link from "next/link";
import { buttonVariants } from "./button";
const Iconss = () => {
const cards = {
aboutCard: {
description:
"Discover more about our services and offerings. We aim to provide the best experience for our users.",
socialLinks: {
x: "https://x.com/SVR_JS",
Mastodon: "https://mastodon.social/@svrjs",
Bluesky: "https://bsky.app/profile/svrjs.org",
Odysee: "https://odysee.com/@SVRJS",
},
},
};
return (
<>
<Link
href={cards.aboutCard.socialLinks.x}
target="_blank"
className={buttonVariants({
variant: "ghost",
size: "sm",
})}
>
<span className="sr-only">X icon</span>
<svg
width="25"
height="25"
viewBox="0 0 25 25"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19.0039 2.34375H22.4512L14.9219 10.9473L23.7793 22.6562H16.8457L11.4111 15.5566L5.2002 22.6562H1.74805L9.7998 13.4521L1.30859 2.34375H8.41797L13.3252 8.83301L19.0039 2.34375ZM17.793 20.5957H19.7021L7.37793 4.29688H5.32715L17.793 20.5957Z"
fill="currentColor"
/>
</svg>
</Link>
<Link
href={cards.aboutCard.socialLinks.Mastodon}
target="_blank"
className={buttonVariants({
variant: "ghost",
size: "sm",
})}
>
<span className="sr-only">Mastodon icon</span>
<svg
width="25"
height="25"
viewBox="0 0 25 25"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M24.163 8.74512C24.163 3.99903 20.6083 2.60743 20.6083 2.60743C17.1206 1.20606 7.85163 1.22071 4.39739 2.60743C4.39739 2.60743 0.842704 3.99903 0.842704 8.74512C0.842704 14.3945 0.4744 21.4111 6.73556 22.8613C8.99561 23.3838 10.9376 23.4961 12.5001 23.418C15.3349 23.2813 16.9253 22.5342 16.9253 22.5342L16.8304 20.7324C16.8304 20.7324 14.8048 21.2891 12.528 21.2256C10.2735 21.1572 7.89627 21.0107 7.52797 18.5889C7.49366 18.3639 7.47688 18.1371 7.47775 17.9102C12.2545 18.9307 16.3338 18.3545 17.4554 18.2373C20.586 17.9102 23.3148 16.2207 23.6608 14.6777C24.2077 12.2461 24.163 8.74512 24.163 8.74512ZM19.9722 14.8584H17.3717V9.28223C17.3717 6.85548 13.8003 6.7627 13.8003 9.61915V12.6709H11.2166V9.61915C11.2166 6.7627 7.64516 6.85548 7.64516 9.28223V14.8584H5.03355C5.03355 8.89649 4.74337 7.63673 6.06034 6.31348C7.50565 4.90235 10.5135 4.80958 11.8527 6.61134L12.5001 7.56348L13.1474 6.61134C14.4923 4.79981 17.5057 4.91212 18.9398 6.31348C20.2623 7.64649 19.9666 8.90137 19.9666 14.8584H19.9722Z"
fill="currentColor"
/>
</svg>
</Link>
<Link
href={cards.aboutCard.socialLinks.Bluesky}
target="_blank"
className={buttonVariants({
variant: "ghost",
size: "sm",
})}
>
<span className="sr-only">Bluesky icon</span>
<svg
width="25"
height="25"
viewBox="0 0 576 512"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M407.8 294.7C404.5 294.3 401.1 293.9 397.8 293.4C401.2 293.8 404.5 294.3 407.8 294.7ZM288 227.1C261.9 176.4 190.9 81.9 124.9 35.3C61.6 -9.40003 37.5 -1.70003 21.6 5.49997C3.3 13.8 0 41.9 0 58.4C0 74.9 9.1 194 15 213.9C34.5 279.6 104.1 301.8 168.2 294.6C171.5 294.1 174.8 293.7 178.2 293.2C174.9 293.7 171.6 294.2 168.2 294.6C74.3 308.6 -9.1 342.8 100.3 464.5C220.6 589.1 265.1 437.8 288 361.1C310.9 437.8 337.2 583.6 473.6 464.5C576 361.1 501.7 308.5 407.8 294.6C404.5 294.2 401.1 293.8 397.8 293.3C401.2 293.7 404.5 294.2 407.8 294.6C471.9 301.7 541.4 279.5 561 213.9C566.9 194 576 75 576 58.4C576 41.8 572.7 13.7 554.4 5.49997C538.6 -1.60003 514.4 -9.40003 451.2 35.3C385.1 81.9 314.1 176.4 288 227.1Z"
fill="currentColor"
/>
</svg>
</Link>
<Link
href={cards.aboutCard.socialLinks.Odysee}
target="_blank"
className={buttonVariants({
variant: "ghost",
size: "sm",
})}
>
<span className="sr-only">Odysee icon</span>
<svg
width="25"
height="25"
viewBox="0 0 25 25"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M19.8584 22.6074C17.793 24.1113 15.249 25 12.5 25C7.0752 25 2.45605 21.543 0.727539 16.709C0.844727 16.792 1.01562 16.8848 1.11328 16.9238C1.90918 17.2852 3.07129 16.6602 4.18457 15.5225C4.52148 15.1855 4.8877 14.917 5.29785 14.6777C6.19141 14.0967 7.13379 13.6621 8.14941 13.3496C8.14941 13.3496 9.23828 15.0195 10.2539 17.002C11.2695 18.9844 9.16504 19.6387 8.92578 19.6387C8.91113 19.6387 8.88672 19.6387 8.85254 19.6338C8.31543 19.6094 5.43457 19.4873 6.11816 22.1289C6.8457 24.9316 10.8789 23.916 12.9346 22.5635C14.9902 21.2109 14.4824 16.7871 14.4824 16.7871C16.4893 16.4746 17.1191 18.5986 17.3096 19.6875C17.3486 19.9121 17.3633 20.1709 17.3779 20.4443C17.4316 21.4795 17.4902 22.6709 19.1016 22.71C19.3604 22.71 19.6191 22.6709 19.8584 22.6123V22.6074ZM15.2051 21.4502C15.1074 21.4258 15.0342 21.3281 15.0586 21.2061C15.1074 21.084 15.2051 21.0352 15.3027 21.0596C15.4004 21.084 15.4736 21.2061 15.4492 21.3037C15.4248 21.4014 15.3271 21.4746 15.2051 21.4502ZM5.09766 16.7822C5.1709 16.7578 5.26855 16.8311 5.29297 16.9287C5.29297 17.0264 5.24414 17.124 5.14648 17.124C5.07324 17.1484 4.97559 17.0752 4.95117 16.9775C4.92676 16.9043 5 16.8066 5.09766 16.7822ZM22.0605 20.5566C23.8916 18.3789 25 15.5664 25 12.5C25 9.2041 23.7256 6.20605 21.6406 3.96973C21.6357 5.11719 21.3428 6.32324 20.8203 7.34863C20.2393 8.33984 18.4277 10.2246 17.4121 11.1914C17.3779 11.2061 17.3584 11.2354 17.3389 11.2598C17.3291 11.2695 17.3242 11.2793 17.3145 11.2891C17.0703 11.626 17.1191 12.1094 17.4609 12.3535C18.501 13.125 20.2148 14.5801 20.3613 15.9082C20.5322 17.6123 21.7236 19.5996 22.0313 20.1172C22.0703 20.1807 22.0947 20.2197 22.0996 20.2344C22.0996 20.3418 22.0801 20.4443 22.0605 20.5518V20.5566ZM19.0771 12.2559C19.0527 12.4023 19.126 12.5439 19.2725 12.5684C19.4189 12.5928 19.5605 12.5195 19.585 12.373C19.6094 12.2266 19.5361 12.085 19.3896 12.0605C19.2432 12.0117 19.1016 12.1094 19.0771 12.2559ZM22.0752 9.28223L21.5186 9.5459L21.3721 10.1758L21.1084 9.61914L20.4785 9.47266L21.0352 9.20898L21.1816 8.5791L21.4453 9.13574L22.0752 9.28223ZM19.3115 2.0166C18.5205 2.41699 18.2324 3.61816 17.8955 5.01465C17.8809 5.08301 17.8613 5.15137 17.8467 5.21973C17.3828 7.09961 16.3525 7.05566 15.8105 7.03613C15.7568 7.03613 15.7129 7.03125 15.6689 7.03125C15.4199 7.03125 15.376 6.83594 15.2344 6.19629C15.1074 5.60547 14.8975 4.63379 14.3604 3.09082C13.252 -0.12207 10.3223 0.678711 8.10059 2.00684C5.40039 3.62305 6.4209 6.98242 7.13379 9.30176C7.16797 9.40918 7.20215 9.5166 7.23145 9.62402C7.03613 9.81934 6.55762 9.99023 5.96191 10.2051C5.37109 10.415 4.66309 10.6689 3.99414 11.0303C2.33887 11.9092 0.561523 13.4229 0.0976562 14.0869C0.0341797 13.5645 0 13.0371 0 12.5C0 5.5957 5.5957 0 12.5 0C15.0098 0 17.3535 0.742187 19.3115 2.0166ZM2.87598 9.25781C2.80273 9.16016 2.65625 9.11133 2.56348 9.18457C2.4707 9.25781 2.41699 9.4043 2.49023 9.49707C2.56348 9.58984 2.70996 9.64355 2.80273 9.57031C2.9248 9.49707 2.94922 9.35059 2.87598 9.25781ZM15.9814 3.16895C16.0791 3.0957 16.2256 3.14453 16.2939 3.24219C16.3672 3.36426 16.3428 3.50586 16.2207 3.55469C16.123 3.62793 15.9766 3.5791 15.9082 3.48145C15.8398 3.38379 15.8838 3.2373 15.9814 3.16895ZM4.64355 5.12695C4.61914 5.2002 4.66797 5.27344 4.74121 5.27344C4.81445 5.29785 4.8877 5.24902 4.8877 5.17578C4.91211 5.10254 4.86328 5.0293 4.79004 5.0293C4.7168 5.0293 4.64355 5.05371 4.64355 5.12695ZM8.7793 5.10254C8.6084 2.99805 10.5908 2.46582 10.5908 2.46582C12.7441 1.71387 13.3252 2.75391 13.833 4.30176C14.3408 5.84961 13.9795 6.38184 11.9482 7.17773C9.91699 7.97363 8.92578 6.95801 8.7793 5.09766V5.10254ZM13.1543 5.83008H13.2031C13.3252 5.83008 13.4473 5.73242 13.4473 5.58594C13.5449 5.24902 13.4961 4.88281 13.3496 4.57031C13.2764 4.47266 13.1543 4.39941 13.0371 4.44824C12.8906 4.49707 12.8174 4.64355 12.8662 4.78516C12.9639 5.00488 13.0127 5.26855 12.9395 5.5127C12.915 5.65918 13.0127 5.80078 13.1592 5.8252L13.1543 5.83008ZM12.6709 3.79883C12.5732 3.79883 12.4756 3.75 12.4268 3.65234C12.3779 3.55469 12.3291 3.48145 12.2803 3.4082C12.1826 3.31055 12.1826 3.14453 12.2803 3.04688C12.3779 2.94922 12.5439 2.94922 12.6416 3.04688C12.7393 3.16895 12.8125 3.29102 12.8857 3.4082C12.959 3.52539 12.9102 3.69629 12.7637 3.76953C12.7344 3.76953 12.7148 3.7793 12.7002 3.78418C12.6904 3.78906 12.6807 3.79395 12.6709 3.79395V3.79883Z"
fill="currentColor"
/>
</svg>
</Link>
</>
);
};
export default Iconss;

25
components/ui/input.tsx Normal file
View file

@ -0,0 +1,25 @@
import * as React from "react"
import { cn } from "@/lib/utils"
export interface InputProps
extends React.InputHTMLAttributes<HTMLInputElement> {}
const Input = React.forwardRef<HTMLInputElement, InputProps>(
({ className, type, ...props }, ref) => {
return (
<input
type={type}
className={cn(
"flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
)}
ref={ref}
{...props}
/>
)
}
)
Input.displayName = "Input"
export { Input }

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

@ -0,0 +1,128 @@
import * as React from "react"
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
import { cva } from "class-variance-authority"
import { ChevronDown } from "lucide-react"
import { cn } from "@/lib/utils"
const NavigationMenu = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Root>
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Root
ref={ref}
className={cn(
"relative z-10 flex max-w-max flex-1 items-center justify-center",
className
)}
{...props}
>
{children}
<NavigationMenuViewport />
</NavigationMenuPrimitive.Root>
))
NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName
const NavigationMenuList = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.List>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.List>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.List
ref={ref}
className={cn(
"group flex flex-1 list-none items-center justify-center space-x-1",
className
)}
{...props}
/>
))
NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName
const NavigationMenuItem = NavigationMenuPrimitive.Item
const navigationMenuTriggerStyle = cva(
"group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50"
)
const NavigationMenuTrigger = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Trigger>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Trigger>
>(({ className, children, ...props }, ref) => (
<NavigationMenuPrimitive.Trigger
ref={ref}
className={cn(navigationMenuTriggerStyle(), "group", className)}
{...props}
>
{children}{" "}
<ChevronDown
className="relative top-[1px] ml-1 h-3 w-3 transition duration-200 group-data-[state=open]:rotate-180"
aria-hidden="true"
/>
</NavigationMenuPrimitive.Trigger>
))
NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName
const NavigationMenuContent = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Content>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Content>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.Content
ref={ref}
className={cn(
"left-0 top-0 w-full data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 md:absolute md:w-auto ",
className
)}
{...props}
/>
))
NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName
const NavigationMenuLink = NavigationMenuPrimitive.Link
const NavigationMenuViewport = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Viewport>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Viewport>
>(({ className, ...props }, ref) => (
<div className={cn("absolute left-0 top-full flex justify-center")}>
<NavigationMenuPrimitive.Viewport
className={cn(
"origin-top-center relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 md:w-[var(--radix-navigation-menu-viewport-width)]",
className
)}
ref={ref}
{...props}
/>
</div>
))
NavigationMenuViewport.displayName =
NavigationMenuPrimitive.Viewport.displayName
const NavigationMenuIndicator = React.forwardRef<
React.ElementRef<typeof NavigationMenuPrimitive.Indicator>,
React.ComponentPropsWithoutRef<typeof NavigationMenuPrimitive.Indicator>
>(({ className, ...props }, ref) => (
<NavigationMenuPrimitive.Indicator
ref={ref}
className={cn(
"top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in",
className
)}
{...props}
>
<div className="relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm bg-border shadow-md" />
</NavigationMenuPrimitive.Indicator>
))
NavigationMenuIndicator.displayName =
NavigationMenuPrimitive.Indicator.displayName
export {
navigationMenuTriggerStyle,
NavigationMenu,
NavigationMenuList,
NavigationMenuItem,
NavigationMenuContent,
NavigationMenuTrigger,
NavigationMenuLink,
NavigationMenuIndicator,
NavigationMenuViewport,
}

View file

@ -0,0 +1,31 @@
"use client"
import * as React from "react"
import * as SeparatorPrimitive from "@radix-ui/react-separator"
import { cn } from "@/lib/utils"
const Separator = React.forwardRef<
React.ElementRef<typeof SeparatorPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
>(
(
{ className, orientation = "horizontal", decorative = true, ...props },
ref
) => (
<SeparatorPrimitive.Root
ref={ref}
decorative={decorative}
orientation={orientation}
className={cn(
"shrink-0 bg-border",
orientation === "horizontal" ? "h-[1px] w-full" : "h-full w-[1px]",
className
)}
{...props}
/>
)
)
Separator.displayName = SeparatorPrimitive.Root.displayName
export { Separator }

140
components/ui/sheet.tsx Normal file
View file

@ -0,0 +1,140 @@
"use client";
import * as React from "react";
import * as SheetPrimitive from "@radix-ui/react-dialog";
import { cva, type VariantProps } from "class-variance-authority";
import { X } from "lucide-react";
import { cn } from "@/lib/utils";
const Sheet = SheetPrimitive.Root;
const SheetTrigger = SheetPrimitive.Trigger;
const SheetClose = SheetPrimitive.Close;
const SheetPortal = SheetPrimitive.Portal;
const SheetOverlay = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Overlay>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Overlay>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Overlay
className={cn(
"fixed inset-0 z-50 bg-black/80 data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0",
className
)}
{...props}
ref={ref}
/>
));
SheetOverlay.displayName = SheetPrimitive.Overlay.displayName;
const sheetVariants = cva(
"fixed z-50 gap-4 bg-background p-6 shadow-lg transition ease-in-out data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:duration-500",
{
variants: {
side: {
top: "inset-x-0 top-0 border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
bottom:
"inset-x-0 bottom-0 border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
left: "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
right:
"inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
},
},
defaultVariants: {
side: "right",
},
}
);
interface SheetContentProps
extends React.ComponentPropsWithoutRef<typeof SheetPrimitive.Content>,
VariantProps<typeof sheetVariants> {}
const SheetContent = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Content>,
SheetContentProps
>(({ side = "right", className, children, ...props }, ref) => (
<SheetPortal>
<SheetOverlay />
<SheetPrimitive.Content
ref={ref}
className={cn(sheetVariants({ side }), className)}
{...props}
>
{children}
<SheetPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-secondary">
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</SheetPrimitive.Close>
</SheetPrimitive.Content>
</SheetPortal>
));
SheetContent.displayName = SheetPrimitive.Content.displayName;
const SheetHeader = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col space-y-2 text-center sm:text-left",
className
)}
{...props}
/>
);
SheetHeader.displayName = "SheetHeader";
const SheetFooter = ({
className,
...props
}: React.HTMLAttributes<HTMLDivElement>) => (
<div
className={cn(
"flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
className
)}
{...props}
/>
);
SheetFooter.displayName = "SheetFooter";
const SheetTitle = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Title>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Title>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Title
ref={ref}
className={cn("text-lg font-semibold text-foreground", className)}
{...props}
/>
));
SheetTitle.displayName = SheetPrimitive.Title.displayName;
const SheetDescription = React.forwardRef<
React.ElementRef<typeof SheetPrimitive.Description>,
React.ComponentPropsWithoutRef<typeof SheetPrimitive.Description>
>(({ className, ...props }, ref) => (
<SheetPrimitive.Description
ref={ref}
className={cn("text-sm text-muted-foreground", className)}
{...props}
/>
));
SheetDescription.displayName = SheetPrimitive.Description.displayName;
export {
Sheet,
SheetPortal,
SheetOverlay,
SheetTrigger,
SheetClose,
SheetContent,
SheetHeader,
SheetFooter,
SheetTitle,
SheetDescription,
};

Some files were not shown because too many files have changed in this diff Show more