commit e56a469cfa5a5a900cd749badea8626d45d63017 Author: Reihan Date: Fri Apr 18 21:32:20 2025 +0700 init diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..56574fe --- /dev/null +++ b/.gitignore @@ -0,0 +1,45 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.* +.yarn/* +!.yarn/patches +!.yarn/plugins +!.yarn/releases +!.yarn/versions + +package-lock.json +yarn.lock +pnpm-lock.yaml + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# env files (can opt-in for committing if needed) +.env* + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts diff --git a/README.md b/README.md new file mode 100644 index 0000000..b396f56 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +# Vertex Dashboard diff --git a/eslint.config.mjs b/eslint.config.mjs new file mode 100644 index 0000000..c85fb67 --- /dev/null +++ b/eslint.config.mjs @@ -0,0 +1,16 @@ +import { dirname } from "path"; +import { fileURLToPath } from "url"; +import { FlatCompat } from "@eslint/eslintrc"; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const compat = new FlatCompat({ + baseDirectory: __dirname, +}); + +const eslintConfig = [ + ...compat.extends("next/core-web-vitals", "next/typescript"), +]; + +export default eslintConfig; diff --git a/next.config.js b/next.config.js new file mode 100644 index 0000000..658404a --- /dev/null +++ b/next.config.js @@ -0,0 +1,4 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = {}; + +module.exports = nextConfig; diff --git a/package.json b/package.json new file mode 100644 index 0000000..fe49948 --- /dev/null +++ b/package.json @@ -0,0 +1,35 @@ +{ + "name": "vertex-dapp", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@geist-ui/core": "^2.3.8", + "@privy-io/react-auth": "^2.0.4", + "chart.js": "^4.4.7", + "framer-motion": "^11.18.1", + "geist": "^1.3.1", + "next": "^14.2.23", + "react": "^18.2.0", + "react-chartjs-2": "^5.3.0", + "react-dom": "^18.2.0", + "use-sync-external-store": "^1.2.0", + "zustand": "^4.5.2" + }, + "devDependencies": { + "@eslint/eslintrc": "^3", + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "eslint": "^8.0.0", + "eslint-config-next": "14.1.0", + "postcss": "^8", + "tailwindcss": "^3.4.1", + "typescript": "^5" + } +} diff --git a/postcss.config.mjs b/postcss.config.mjs new file mode 100644 index 0000000..1a69fd2 --- /dev/null +++ b/postcss.config.mjs @@ -0,0 +1,8 @@ +/** @type {import('postcss-load-config').Config} */ +const config = { + plugins: { + tailwindcss: {}, + }, +}; + +export default config; diff --git a/public/bg.svg b/public/bg.svg new file mode 100644 index 0000000..df4fffe --- /dev/null +++ b/public/bg.svg @@ -0,0 +1,17 @@ + + + + + + + + + + + + + + + + + diff --git a/public/og-banner.png b/public/og-banner.png new file mode 100644 index 0000000..78d3c5b Binary files /dev/null and b/public/og-banner.png differ diff --git a/src/app/(login)/Form.tsx b/src/app/(login)/Form.tsx new file mode 100644 index 0000000..9946252 --- /dev/null +++ b/src/app/(login)/Form.tsx @@ -0,0 +1,180 @@ +"use client"; +import Image from "next/image"; +import iconImg from "@/assets/images/brand/icon.svg"; +import BtnPrimary from "@/components/BtnPrimary"; +import Link from "next/link"; +import SocialLinks from "@/components/SocialLinks"; +import { motion } from "framer-motion"; +import { usePrivy } from "@privy-io/react-auth"; +import { useRouter } from "next/navigation"; +import { useEffect } from "react"; + +export default function Form() { + const { ready, login, authenticated } = usePrivy(); + const router = useRouter(); + + useEffect(() => { + if (ready && authenticated) { + router.push("/dashboard/home"); + } + }, [ready, authenticated, router]); + + if (!ready) { + return <>; + } + + return ( + + + icon + + + + Welcome to Vertex + + + + Get access to dashboard + + + +
+ +
+
+ + + By signing up, I agree with Vertex's{" "} + + Terms and Conditions + {" "} + &{" "} + + Privacy Policy + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ); +} diff --git a/src/app/(login)/page.tsx b/src/app/(login)/page.tsx new file mode 100644 index 0000000..7037c2e --- /dev/null +++ b/src/app/(login)/page.tsx @@ -0,0 +1,9 @@ +import Form from "./Form"; + +export default function Login() { + return ( +
+
+
+ ); +} diff --git a/src/app/dashboard/api/Box.tsx b/src/app/dashboard/api/Box.tsx new file mode 100644 index 0000000..0c44bd2 --- /dev/null +++ b/src/app/dashboard/api/Box.tsx @@ -0,0 +1,81 @@ +"use client"; +import { motion } from "framer-motion"; + +export default function Box() { + return ( + +

+ Team Vertex is working on this +

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ); +} diff --git a/src/app/dashboard/api/page.tsx b/src/app/dashboard/api/page.tsx new file mode 100644 index 0000000..becee69 --- /dev/null +++ b/src/app/dashboard/api/page.tsx @@ -0,0 +1,24 @@ +import type { Metadata } from "next"; +import Box from "./Box"; + +export const metadata: Metadata = { + title: "Vertex - API & Webhook", + description: + "Vertex thrives on the power of community. By connecting users across the globe, we are creating a network that fosters collaboration, innovation, and shared success.", + openGraph: { + type: "website", + url: "https://dapp-ai-proj-02.vercel.app/dashboard/api", + title: "Vertex - API & Webhook", + description: + "Vertex thrives on the power of community. By connecting users across the globe, we are creating a network that fosters collaboration, innovation, and shared success.", + images: "https://dapp-ai-proj-02.vercel.app/og-banner.png", + }, +}; + +export default function API() { + return ( +
+ +
+ ); +} diff --git a/src/app/dashboard/contact/Box.tsx b/src/app/dashboard/contact/Box.tsx new file mode 100644 index 0000000..cec91bf --- /dev/null +++ b/src/app/dashboard/contact/Box.tsx @@ -0,0 +1,268 @@ +"use client"; +import { motion } from "framer-motion"; +import BtnPrimary from "@/components/BtnPrimary"; +import { useState } from "react"; + +export default function Box() { + // Add state management + const [formData, setFormData] = useState({ + name: "", + email: "", + subject: "", + message: "", + }); + const [buttonText, setButtonText] = useState("Submit Form"); + + // Handle form submission + const handleSubmit = () => { + // Check if all fields are filled + if (!formData.name || !formData.email || !formData.subject || !formData.message) { + alert("Please fill in all fields"); + return; + } + + // Wait 1 second before changing text + setTimeout(() => { + setButtonText("Form Submitted"); + + // Reset form after 2 seconds of showing "Form Submitted" + setTimeout(() => { + setButtonText("Submit Form"); + setFormData({ + name: "", + email: "", + subject: "", + message: "", + }); + }, 2000); + }, 1000); + }; + + return ( + +

Reach Out to Team Vertex

+ +

Our support team is available to assist you

+ +
+
+

Your name

+ +
+ setFormData((prev) => ({ ...prev, name: e.target.value }))} + className="w-full px-3.5 bg-transparent text-sm uppercase text-[#0CE77E] placeholder:text-[#0CE77E]/25" + placeholder="Enter your name" + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ +
+

Your email address

+ +
+ setFormData((prev) => ({ ...prev, email: e.target.value }))} + className="w-full px-3.5 bg-transparent text-sm uppercase text-[#0CE77E] placeholder:text-[#0CE77E]/25" + placeholder="Enter your email" + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+

Subject

+ +
+ setFormData((prev) => ({ ...prev, subject: e.target.value }))} + className="w-full px-3.5 bg-transparent text-sm uppercase text-[#0CE77E] placeholder:text-[#0CE77E]/25" + placeholder="I am facing issue with ..." + /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+

Message

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+
+ +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ); +} diff --git a/src/app/dashboard/contact/page.tsx b/src/app/dashboard/contact/page.tsx new file mode 100644 index 0000000..b9e9b41 --- /dev/null +++ b/src/app/dashboard/contact/page.tsx @@ -0,0 +1,24 @@ +import type { Metadata } from "next"; +import Box from "./Box"; + +export const metadata: Metadata = { + title: "Vertex - Contact Us", + description: + "Vertex thrives on the power of community. By connecting users across the globe, we are creating a network that fosters collaboration, innovation, and shared success.", + openGraph: { + type: "website", + url: "https://dapp-ai-proj-02.vercel.app/dashboard/server", + title: "Vertex - Contact Us", + description: + "Vertex thrives on the power of community. By connecting users across the globe, we are creating a network that fosters collaboration, innovation, and shared success.", + images: "https://dapp-ai-proj-02.vercel.app/og-banner.png", + }, +}; + +export default function Contact() { + return ( +
+ +
+ ); +} diff --git a/src/app/dashboard/home/BottomBoxes.tsx b/src/app/dashboard/home/BottomBoxes.tsx new file mode 100644 index 0000000..0b9eec1 --- /dev/null +++ b/src/app/dashboard/home/BottomBoxes.tsx @@ -0,0 +1,223 @@ +"use client"; +import Image from "next/image"; +import nvidiaImg from "@/assets/images/manufacturers/nvidia.svg"; +import amdImg from "@/assets/images/manufacturers/amd.svg"; +import intelImg from "@/assets/images/manufacturers/intel.svg"; +import googleImg from "@/assets/images/manufacturers/google.svg"; +import qualcommImg from "@/assets/images/manufacturers/qualcomm.svg"; +import Statistics from "./Statistics"; +import { motion } from "framer-motion"; + +export default function BottomBoxes() { + return ( +
+ +

+ TOP GPUs BY MANUFACTURER +

+ +

+ A detailed list of GPU manufacturing companies worldwide +

+ +
+
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ ); +} diff --git a/src/app/dashboard/home/Boxes.tsx b/src/app/dashboard/home/Boxes.tsx new file mode 100644 index 0000000..73a1311 --- /dev/null +++ b/src/app/dashboard/home/Boxes.tsx @@ -0,0 +1,366 @@ +"use client"; +import Image from "next/image"; +import serverIcon1 from "@/assets/images/server-1-icon.svg"; +import serverIcon2 from "@/assets/images/server-2-icon.svg"; +import serverIcon3 from "@/assets/images/server-3-icon.svg"; +import serverIcon4 from "@/assets/images/server-4-icon.svg"; +import { motion } from "framer-motion"; + +export default function Boxes() { + return ( +
+ +
+ + +

+ Available +

+
+ +
+

GAMING GPUs

+

Consumer grade GPUs

+
+ +
+
+

31%

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + +

+ High Performance +

+
+ +
+

Datacenter GPUs

+

Datacenter based GPUs

+
+ +
+
+

27%

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + +

+ Low Performance +

+
+ +
+

Integrated GPUs

+

Embedded into CPU

+
+ +
+
+

10%

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + +
+ + +

+ Specialised GPU +

+
+ +
+

AI GPUs

+

Manufactured for AI

+
+ +
+
+

36%

+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
+ ); +} diff --git a/src/app/dashboard/home/Statistics.tsx b/src/app/dashboard/home/Statistics.tsx new file mode 100644 index 0000000..c84296b --- /dev/null +++ b/src/app/dashboard/home/Statistics.tsx @@ -0,0 +1,173 @@ +"use client"; +import { Radar } from "react-chartjs-2"; +import { + Chart as ChartJS, + RadialLinearScale, + PointElement, + LineElement, + Filler, + Tooltip, + Legend, +} from "chart.js"; +ChartJS.register( + RadialLinearScale, + PointElement, + LineElement, + Filler, + Tooltip, + Legend +); +import { motion } from "framer-motion"; + +export default function Statistics() { + const data = { + labels: [ + "Cluster Utilization", + "Power Draw", + "NVENC", + "PCIe Throughput", + "GPU Utilization", + "ECC Errors", + "Avg. Temp", + ], + datasets: [ + { + label: "Performance Metrics", + data: [140, 120, 120, 60, 100, 120, 120], + backgroundColor: "rgba(11, 231, 126, 0.2)", + borderColor: "rgba(11, 231, 126, 1)", + borderWidth: 2, + pointBackgroundColor: "rgba(11, 231, 126, 1)", + pointBorderColor: "rgba(0, 0, 0, 1)", + pointHoverRadius: 5, + }, + ], + }; + + const options = { + responsive: true, + plugins: { + tooltip: { + enabled: true, + backgroundColor: "rgba(0, 0, 0, 1)", + titleColor: "rgba(11, 231, 126, 1)", + bodyColor: "rgba(255, 255, 255, 1)", + }, + legend: { + display: false, + }, + }, + scales: { + r: { + ticks: { + display: true, + color: "rgba(11, 231, 126, 0.7)", + backdropColor: "transparent", + }, + grid: { + color: "rgba(11, 231, 126, 0.3)", + }, + angleLines: { + color: "rgba(11, 231, 126, 0.5)", + }, + pointLabels: { + color: "rgba(11, 231, 126, 0.7)", + font: { + size: 12, + }, + }, + }, + }, + }; + + return ( + +

+ Resource Statistics +

+ +

+ How the GPU’s resources are being utilized +

+ +
+
+ +
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ); +} diff --git a/src/app/dashboard/home/page.tsx b/src/app/dashboard/home/page.tsx new file mode 100644 index 0000000..87aa4dc --- /dev/null +++ b/src/app/dashboard/home/page.tsx @@ -0,0 +1,26 @@ +import type { Metadata } from "next"; +import Boxes from "./Boxes"; +import BottomBoxes from "./BottomBoxes"; + +export const metadata: Metadata = { + title: "Vertex - Dashboard Home", + description: + "Vertex thrives on the power of community. By connecting users across the globe, we are creating a network that fosters collaboration, innovation, and shared success.", + openGraph: { + type: "website", + url: "https://dapp-ai-proj-02.vercel.app/dashboard/home", + title: "Vertex - Dashboard Home", + description: + "Vertex thrives on the power of community. By connecting users across the globe, we are creating a network that fosters collaboration, innovation, and shared success.", + images: "https://dapp-ai-proj-02.vercel.app/og-banner.png", + }, +}; + +export default function Home() { + return ( +
+ + +
+ ); +} diff --git a/src/app/dashboard/layout.tsx b/src/app/dashboard/layout.tsx new file mode 100644 index 0000000..8f43610 --- /dev/null +++ b/src/app/dashboard/layout.tsx @@ -0,0 +1,36 @@ +"use client"; + +import Sidebar from "@/components/Sidebar"; +import Topbar from "@/components/Topbar"; +import { usePrivy } from "@privy-io/react-auth"; +import { useRouter } from "next/navigation"; + +export default function DashboardLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + const { ready, authenticated } = usePrivy(); + const router = useRouter(); + if (!ready) { + return <>; + } + if (ready && !authenticated) { + router.push("/"); + return null; + } + return ( +
+
+
+ + +
+ + {children} +
+
+
+
+ ); +} diff --git a/src/app/dashboard/rent/Boxes.tsx b/src/app/dashboard/rent/Boxes.tsx new file mode 100644 index 0000000..2c20989 --- /dev/null +++ b/src/app/dashboard/rent/Boxes.tsx @@ -0,0 +1,234 @@ +"use client"; +import Link from "next/link"; +import { motion } from "framer-motion"; + +interface IGPU { + title: string; + subTitle: string; + price: string; + isRecentlyPurchased?: boolean; + link: string; +} + +export default function Boxes() { + const gpus: IGPU[] = [ + { + title: "NVIDIA RTX A4000", + subTitle: "16GB GDDR6 - 6144 CUDA Cores", + price: "$0.20/hour ($147/mo)", + isRecentlyPurchased: true, + link: "https://nowpayments.io/payment?iid=5964763281", + }, + { + title: "NVIDIA RTX A5000", + subTitle: "24 GB GDDR6 - 8192 CUDA Cores", + price: "$0.26/hr ($190/month)", + link: "https://nowpayments.io/payment?iid=4873013025", + }, + { + title: "4x NVIDIA RTX 4090", + subTitle: "24 GB GDDR6X - 16384 GPU CUDA Cores", + price: "$0.37/hr ($268/month)", + link: "https://nowpayments.io/payment?iid=5977605820", + }, + { + title: "NVIDIA V100", + subTitle: "32 GB HBM2 - 5120 CUDA Cores", + price: "$0.44/hr ($317/month)", + link: "https://nowpayments.io/payment?iid=5437270588", + }, + { + title: "NVIDIA L4 Ada", + subTitle: "24 GB GDDR6 - 7680 CUDA Cores", + price: "$0.52/hr ($372/month)", + link: "https://nowpayments.io/payment?iid=6283623234", + }, + { + title: "NVIDIA RTX A6000", + subTitle: "48 GB GDDR6 - 10752 CUDA Cores", + price: "$0.53/hr ($380/month)", + link: "https://nowpayments.io/payment?iid=6074730706", + }, + { + title: "NVIDIA L40S Ada", + subTitle: "48 GB GDDR6 - 18176 CUDA Cores", + price: "$1.24/hr ($890/month)", + link: "https://nowpayments.io/payment?iid=4570621084", + }, + { + title: "NVIDIA A100 PCIe", + subTitle: "40 GB HBM2 - 6912 CUDA Cores", + price: "$1.62/hr ($1,166/month)", + link: "https://nowpayments.io/payment?iid=6381921922", + }, + { + title: "NVIDIA H100 NVL", + subTitle: "94 GB HBM3 - 14592 CUDA Cores", + price: "$3.35/hr ($2,410/month)", + link: "https://nowpayments.io/payment?iid=5319698362", + }, + { + title: "NVIDIA H100 SXM", + subTitle: "80 GB HBM3 - 14592 CUDA Cores", + price: "$3.60/hr ($2,592/month)", + link: "https://nowpayments.io/payment?iid=4768823499", + }, + ]; + + return ( +
+ {gpus.map((gpu, index) => ( + +
+
+ + + + + + + + + + + + +

{gpu.title}

+
+ +

{gpu.subTitle}

+
+ +
+
+

+ {gpu.price} +

+ + {gpu.isRecentlyPurchased && ( +

+ + + + Recently purchased +

+ )} +
+ + + Rent now + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ))} +
+ ); +} diff --git a/src/app/dashboard/rent/page.tsx b/src/app/dashboard/rent/page.tsx new file mode 100644 index 0000000..7b80a97 --- /dev/null +++ b/src/app/dashboard/rent/page.tsx @@ -0,0 +1,24 @@ +import type { Metadata } from "next"; +import Boxes from "./Boxes"; + +export const metadata: Metadata = { + title: "Vertex - Rent a GPU", + description: + "Vertex thrives on the power of community. By connecting users across the globe, we are creating a network that fosters collaboration, innovation, and shared success.", + openGraph: { + type: "website", + url: "https://dapp-ai-proj-02.vercel.app/dashboard/rent", + title: "Vertex - Rent a GPU", + description: + "Vertex thrives on the power of community. By connecting users across the globe, we are creating a network that fosters collaboration, innovation, and shared success.", + images: "https://dapp-ai-proj-02.vercel.app/og-banner.png", + }, +}; + +export default function Rent() { + return ( +
+ +
+ ); +} diff --git a/src/app/dashboard/server/Box.tsx b/src/app/dashboard/server/Box.tsx new file mode 100644 index 0000000..2c9faec --- /dev/null +++ b/src/app/dashboard/server/Box.tsx @@ -0,0 +1,142 @@ +"use client"; +import { motion } from "framer-motion"; +import Image from "next/image"; +import serverIcon1 from "@/assets/images/server-1-icon.svg"; +import serverIcon2 from "@/assets/images/server-2-icon.svg"; +import serverIcon3 from "@/assets/images/server-3-icon.svg"; +import serverIcon4 from "@/assets/images/server-4-icon.svg"; + +export default function Box() { + return ( + +

Live and preparing servers

+ +

List of our GPUs and their current status

+ +
+
+ + +
+

GAMING GPUs

+

Total GPUs : 52

+
+
+ +
+
+

Ready : 38

+
+ +
+

PREPARING : 14

+
+
+
+ +
+
+ + +
+

Datacenter GPUs

+

Total GPUs : 45

+
+
+ +
+
+

Ready : 33

+
+ +
+

PREPARING : 12

+
+
+
+ +
+
+ + +
+

Integrated GPUs

+

Total GPUs : 17

+
+
+ +
+
+

Ready : 12

+
+ +
+

PREPARING : 5

+
+
+
+ +
+
+ + +
+

AI GPUs

+

Total GPUs : 61

+
+
+ +
+
+

Ready : 44

+
+ +
+

PREPARING : 17

+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ); +} diff --git a/src/app/dashboard/server/page.tsx b/src/app/dashboard/server/page.tsx new file mode 100644 index 0000000..8b07c82 --- /dev/null +++ b/src/app/dashboard/server/page.tsx @@ -0,0 +1,25 @@ +import type { Metadata } from "next"; + +import Box from "./Box"; + +export const metadata: Metadata = { + title: "Vertex - Server Status", + description: + "Vertex thrives on the power of community. By connecting users across the globe, we are creating a network that fosters collaboration, innovation, and shared success.", + openGraph: { + type: "website", + url: "https://dapp-ai-proj-02.vercel.app/dashboard/server", + title: "Vertex - Server Status", + description: + "Vertex thrives on the power of community. By connecting users across the globe, we are creating a network that fosters collaboration, innovation, and shared success.", + images: "https://dapp-ai-proj-02.vercel.app/og-banner.png", + }, +}; + +export default function Server() { + return ( +
+ +
+ ); +} diff --git a/src/app/favicon.ico b/src/app/favicon.ico new file mode 100644 index 0000000..3de7d0f Binary files /dev/null and b/src/app/favicon.ico differ diff --git a/src/app/globals.css b/src/app/globals.css new file mode 100644 index 0000000..fe5d95c --- /dev/null +++ b/src/app/globals.css @@ -0,0 +1,13 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +input, +textarea { + outline: none; + border: none; +} + +.hide-scrollbar::-webkit-scrollbar { + display: none; +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx new file mode 100644 index 0000000..9fb132f --- /dev/null +++ b/src/app/layout.tsx @@ -0,0 +1,37 @@ +import type { Metadata } from "next"; +import { GeistSans, GeistMono } from "geist/font"; +import "./globals.css"; +import Bg from "@/components/Bg"; +import Providers from "@/components/Providers"; + +const geistSans = GeistSans; +const geistMono = GeistMono; + +export const metadata: Metadata = { + title: "Login to Your Dashboard - Vertex", + description: + "Vertex thrives on the power of community. By connecting users across the globe, we are creating a network that fosters collaboration, innovation, and shared success.", + openGraph: { + type: "website", + url: "https://dapp-ai-proj-02.vercel.app", + title: "Login to Your Dashboard - Vertex", + description: + "Vertex thrives on the power of community. By connecting users across the globe, we are creating a network that fosters collaboration, innovation, and shared success.", + images: "https://dapp-ai-proj-02.vercel.app/og-banner.png", + }, +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + {children} + + + + ); +} diff --git a/src/assets/images/brand/icon.svg b/src/assets/images/brand/icon.svg new file mode 100644 index 0000000..c4063ae --- /dev/null +++ b/src/assets/images/brand/icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/src/assets/images/manufacturers/amd.svg b/src/assets/images/manufacturers/amd.svg new file mode 100644 index 0000000..aa8ee32 --- /dev/null +++ b/src/assets/images/manufacturers/amd.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/manufacturers/google.svg b/src/assets/images/manufacturers/google.svg new file mode 100644 index 0000000..584ccba --- /dev/null +++ b/src/assets/images/manufacturers/google.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/assets/images/manufacturers/intel.svg b/src/assets/images/manufacturers/intel.svg new file mode 100644 index 0000000..31e0cce --- /dev/null +++ b/src/assets/images/manufacturers/intel.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/assets/images/manufacturers/nvidia.svg b/src/assets/images/manufacturers/nvidia.svg new file mode 100644 index 0000000..8b455c9 --- /dev/null +++ b/src/assets/images/manufacturers/nvidia.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/manufacturers/qualcomm.svg b/src/assets/images/manufacturers/qualcomm.svg new file mode 100644 index 0000000..1b4fb17 --- /dev/null +++ b/src/assets/images/manufacturers/qualcomm.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/images/server-1-icon.svg b/src/assets/images/server-1-icon.svg new file mode 100644 index 0000000..c8122c2 --- /dev/null +++ b/src/assets/images/server-1-icon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/assets/images/server-2-icon.svg b/src/assets/images/server-2-icon.svg new file mode 100644 index 0000000..e235eab --- /dev/null +++ b/src/assets/images/server-2-icon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/assets/images/server-3-icon.svg b/src/assets/images/server-3-icon.svg new file mode 100644 index 0000000..a5c8985 --- /dev/null +++ b/src/assets/images/server-3-icon.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/assets/images/server-4-icon.svg b/src/assets/images/server-4-icon.svg new file mode 100644 index 0000000..601dc7d --- /dev/null +++ b/src/assets/images/server-4-icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/components/Bg.tsx b/src/components/Bg.tsx new file mode 100644 index 0000000..ab64f7b --- /dev/null +++ b/src/components/Bg.tsx @@ -0,0 +1,44 @@ +"use client"; +import Image from "next/image"; +// import { useEffect, useRef, useState } from "react"; +// import { FlickeringGrid } from "@/components/magicui/flickering-grid"; + +export default function Bg() { + // const [canvasWidth, setCanvasWidth] = useState(0); + // const [canvasHeight, setCanvasHeight] = useState(0); + + // const canvasPlaceholderRef = useRef(null); + + // useEffect(() => { + // if (canvasPlaceholderRef.current) { + // setCanvasWidth(canvasPlaceholderRef.current.clientWidth + 10); + // setCanvasHeight(canvasPlaceholderRef.current.clientHeight + 10); + // } + // }, [canvasPlaceholderRef]); + + return ( +
+
+ + {/* canvasWidth ? 2000 : canvasWidth} + height={2000 > canvasHeight ? 2000 : canvasHeight} + /> */} +
+
+ ); +} diff --git a/src/components/BtnPrimary.tsx b/src/components/BtnPrimary.tsx new file mode 100644 index 0000000..53e26af --- /dev/null +++ b/src/components/BtnPrimary.tsx @@ -0,0 +1,60 @@ +import { JSX } from "react/jsx-runtime"; + +interface IProps { + icon?: JSX.Element; + text: string; + hasRightArrowIcon?: boolean; + customClasses?: string; +} + +export default function BtnPrimary({ icon, text, hasRightArrowIcon, customClasses }: IProps) { + return ( +
+ {icon} + +

{text}

+ + {hasRightArrowIcon && ( + + + + )} + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ); +} diff --git a/src/components/Providers.tsx b/src/components/Providers.tsx new file mode 100644 index 0000000..a28ffe3 --- /dev/null +++ b/src/components/Providers.tsx @@ -0,0 +1,41 @@ +"use client"; + +import { PrivyProvider } from "@privy-io/react-auth"; +import { toSolanaWalletConnectors } from "@privy-io/react-auth/solana"; + +const solanaConnectors = toSolanaWalletConnectors({ + // By default, shouldAutoConnect is enabled + shouldAutoConnect: true, +}); + +export default function Providers({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} diff --git a/src/components/Sidebar.tsx b/src/components/Sidebar.tsx new file mode 100644 index 0000000..0eac801 --- /dev/null +++ b/src/components/Sidebar.tsx @@ -0,0 +1,415 @@ +"use client"; +import Image from "next/image"; +import iconImg from "@/assets/images/brand/icon.svg"; +import Link from "next/link"; +import { JSX } from "react/jsx-runtime"; +import { usePathname } from "next/navigation"; +import { usePrivy } from "@privy-io/react-auth"; +import { useState } from "react"; + +interface ILinksPart { + title: string; + links: ILink[]; +} + +interface ILink { + icon: JSX.Element; + title: string; + href: string; + subTitle?: string; + target?: string; +} + +export default function Sidebar() { + const pathname = usePathname(); + const { logout, user } = usePrivy(); + const email = user?.email?.address; + const discordUsername = user?.discord?.username; + const address = user?.wallet ? user.wallet.address : ""; + const [copied, setCopied] = useState(false); + + const formatAddress = (address: string | undefined) => { + if (!address) return "user"; + return `${address.slice(0, 4)}...${address.slice(-5)}`; + }; + + const handleCopyAddress = () => { + if (address) { + navigator.clipboard.writeText(address); + setCopied(true); + setTimeout(() => { + setCopied(false); + }, 2000); + } + }; + + const linksParts: ILinksPart[] = [ + { + title: "Dashboard", + links: [ + { + icon: ( + + + + ), + title: "Home", + href: "/dashboard/home", + }, + { + icon: ( + + + + + + ), + title: "Rent a GPU", + href: "/dashboard/rent", + }, + ], + }, + { + title: "Details", + links: [ + { + icon: ( + + + + + + + ), + title: "API & Webhook", + href: "/dashboard/api", + subTitle: "Q2", + }, + { + icon: ( + + + + + + + + + ), + title: "Server Status", + href: "/dashboard/server", + }, + ], + }, + { + title: "Support", + links: [ + { + icon: ( + + + + + + + ), + title: "Contact us", + href: "/dashboard/contact", + }, + { + icon: ( + + + + ), + title: "Twitter/X", + href: "https://x.com/vertex_gpu", + target: "_blank", + }, + { + icon: ( + + + + ), + title: "Docs", + href: "https://docs.vertexgpu.com", + target: "_blank", + }, + ], + }, + ]; + + return ( +
+
+
+ + + +
+ + {linksParts.map((linksPart, index) => { + return ( +
+

{linksPart.title}

+ +
+ {linksPart.links.map((link, linkIndex) => { + return ( + + {link.icon} + {link.title} + + {link.subTitle && ( + + {link.subTitle} + + )} + + {pathname === link.href && ( + <> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + )} + + ); + })} +
+
+ ); + })} +
+ +
+
+ + + + + +
+ {email && {email.split("@")[0]}} + {discordUsername && !email && ( + {discordUsername} + )} + {address && !email && !discordUsername && ( + + )} +
+
+ +
+ + +
+ +
+
+
+
+ ); +} diff --git a/src/components/SocialLinks.tsx b/src/components/SocialLinks.tsx new file mode 100644 index 0000000..6a6d173 --- /dev/null +++ b/src/components/SocialLinks.tsx @@ -0,0 +1,65 @@ +"use client"; +import { motion } from "framer-motion"; + +export default function SocialLinks() { + return ( +
+ + + + + + + + + + + +
+ ); +} diff --git a/src/components/Topbar.tsx b/src/components/Topbar.tsx new file mode 100644 index 0000000..bafce40 --- /dev/null +++ b/src/components/Topbar.tsx @@ -0,0 +1,131 @@ +"use client"; +import { usePathname } from "next/navigation"; +import { motion } from "framer-motion"; +import { usePrivy } from "@privy-io/react-auth"; + +export default function Topbar() { + const pathname = usePathname(); + const { user } = usePrivy(); + const email = user?.email?.address; + const discordUsername = user?.discord?.username; + const address = user?.wallet?.address; + + const formatAddress = (address: string | undefined) => { + if (!address) return "user"; + return `${address.slice(0, 4)}...${address.slice(-5)}`; + }; + + const getUsername = () => { + if (email) return email.split("@")[0]; + if (discordUsername) return discordUsername; + return formatAddress(address); + }; + + return ( +
+ + {pathname === "/dashboard/home" && `Good Afternoon, ${getUsername()}`} + {pathname === "/dashboard/rent" && "Rent a GPU"} + {pathname === "/dashboard/api" && "API & Webhooks"} + {pathname === "/dashboard/server" && "Server Status"} + {pathname === "/dashboard/contact" && "Contact Us"} + + +
+ {/* + + + + English + + + + */} + + { + if (document.fullscreenElement) { + document.exitFullscreen(); + } else { + document.documentElement.requestFullscreen(); + } + }} + className="w-[40px] h-[40px] border border-white/5 bg-[#0E1618] flex justify-center items-center" + > + + + + +
+
+ ); +} diff --git a/src/components/magicui/flickering-grid.tsx b/src/components/magicui/flickering-grid.tsx new file mode 100644 index 0000000..033ed41 --- /dev/null +++ b/src/components/magicui/flickering-grid.tsx @@ -0,0 +1,193 @@ +"use client"; + +import React, { + useCallback, + useEffect, + useMemo, + useRef, + useState, +} from "react"; + +interface FlickeringGridProps { + squareSize?: number; + gridGap?: number; + flickerChance?: number; + color?: string; + width?: number; + height?: number; + className?: string; + maxOpacity?: number; +} + +export const FlickeringGrid: React.FC = ({ + squareSize = 4, + gridGap = 6, + flickerChance = 0.3, + color = "rgb(0, 0, 0)", + width, + height, + className, + maxOpacity = 0.3, +}) => { + const canvasRef = useRef(null); + const containerRef = useRef(null); + const [isInView, setIsInView] = useState(false); + const [canvasSize, setCanvasSize] = useState({ width: 0, height: 0 }); + + const memoizedColor = useMemo(() => { + const toRGBA = (color: string) => { + if (typeof window === "undefined") { + return `rgba(0, 0, 0,`; + } + const canvas = document.createElement("canvas"); + canvas.width = canvas.height = 1; + const ctx = canvas.getContext("2d"); + if (!ctx) return "rgba(255, 0, 0,"; + ctx.fillStyle = color; + ctx.fillRect(0, 0, 1, 1); + const [r, g, b] = Array.from(ctx.getImageData(0, 0, 1, 1).data); + return `rgba(${r}, ${g}, ${b},`; + }; + return toRGBA(color); + }, [color]); + + const setupCanvas = useCallback( + (canvas: HTMLCanvasElement, width: number, height: number) => { + const dpr = window.devicePixelRatio || 1; + canvas.width = width * dpr; + canvas.height = height * dpr; + canvas.style.width = `${width}px`; + canvas.style.height = `${height}px`; + const cols = Math.floor(width / (squareSize + gridGap)); + const rows = Math.floor(height / (squareSize + gridGap)); + + const squares = new Float32Array(cols * rows); + for (let i = 0; i < squares.length; i++) { + squares[i] = Math.random() * maxOpacity; + } + + return { cols, rows, squares, dpr }; + }, + [squareSize, gridGap, maxOpacity] + ); + + const updateSquares = useCallback( + (squares: Float32Array, deltaTime: number) => { + for (let i = 0; i < squares.length; i++) { + if (Math.random() < flickerChance * deltaTime) { + squares[i] = Math.random() * maxOpacity; + } + } + }, + [flickerChance, maxOpacity] + ); + + const drawGrid = useCallback( + ( + ctx: CanvasRenderingContext2D, + width: number, + height: number, + cols: number, + rows: number, + squares: Float32Array, + dpr: number + ) => { + ctx.clearRect(0, 0, width, height); + ctx.fillStyle = "transparent"; + ctx.fillRect(0, 0, width, height); + + for (let i = 0; i < cols; i++) { + for (let j = 0; j < rows; j++) { + const opacity = squares[i * rows + j]; + ctx.fillStyle = `${memoizedColor}${opacity})`; + ctx.fillRect( + i * (squareSize + gridGap) * dpr, + j * (squareSize + gridGap) * dpr, + squareSize * dpr, + squareSize * dpr + ); + } + } + }, + [memoizedColor, squareSize, gridGap] + ); + + useEffect(() => { + const canvas = canvasRef.current; + const container = containerRef.current; + if (!canvas || !container) return; + + const ctx = canvas.getContext("2d"); + if (!ctx) return; + + let animationFrameId: number; + let gridParams: ReturnType; + + const updateCanvasSize = () => { + const newWidth = width || container.clientWidth; + const newHeight = height || container.clientHeight; + setCanvasSize({ width: newWidth, height: newHeight }); + gridParams = setupCanvas(canvas, newWidth, newHeight); + }; + + updateCanvasSize(); + + let lastTime = 0; + const animate = (time: number) => { + if (!isInView) return; + + const deltaTime = (time - lastTime) / 1000; + lastTime = time; + + updateSquares(gridParams.squares, deltaTime); + drawGrid( + ctx, + canvas.width, + canvas.height, + gridParams.cols, + gridParams.rows, + gridParams.squares, + gridParams.dpr + ); + animationFrameId = requestAnimationFrame(animate); + }; + + const resizeObserver = new ResizeObserver(() => { + updateCanvasSize(); + }); + + resizeObserver.observe(container); + + const intersectionObserver = new IntersectionObserver( + ([entry]) => { + setIsInView(entry.isIntersecting); + }, + { threshold: 0 } + ); + + intersectionObserver.observe(canvas); + + if (isInView) { + animationFrameId = requestAnimationFrame(animate); + } + + return () => { + cancelAnimationFrame(animationFrameId); + resizeObserver.disconnect(); + intersectionObserver.disconnect(); + }; + }, [setupCanvas, updateSquares, drawGrid, width, height, isInView]); + + return ( +
+ +
+ ); +}; diff --git a/tailwind.config.ts b/tailwind.config.ts new file mode 100644 index 0000000..b6418f4 --- /dev/null +++ b/tailwind.config.ts @@ -0,0 +1,14 @@ +import type { Config } from "tailwindcss"; + +export default { + content: [ + "./src/pages/**/*.{js,ts,jsx,tsx,mdx}", + "./src/components/**/*.{js,ts,jsx,tsx,mdx}", + "./src/app/**/*.{js,ts,jsx,tsx,mdx}", + ], + theme: { + extend: { + }, + }, + plugins: [], +} satisfies Config; diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..c133409 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,27 @@ +{ + "compilerOptions": { + "target": "ES2017", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +}