From 6a44aa01532b8c9335c73c26a1208a6830536f59 Mon Sep 17 00:00:00 2001 From: MacRimi Date: Thu, 13 Feb 2025 17:28:49 +0100 Subject: [PATCH] start web --- .gitignore | 16 +++ web/app/components/.DS_Store | Bin 0 -> 6148 bytes web/app/components/CTA.tsx | 18 +++ web/app/components/DocSidebar.tsx | 36 +++++ web/app/components/Features.tsx | 44 ++++++ web/app/components/Footer.tsx | 76 ++++++++++ web/app/components/Header.tsx | 27 ++++ web/app/components/Hero.tsx | 23 +++ web/app/components/Navbar.tsx | 47 +++++++ web/app/components/Pricing.tsx | 53 +++++++ web/app/components/ProductPreview.tsx | 21 +++ web/app/components/Testimonials.tsx | 37 +++++ web/app/components/ui/.DS_Store | Bin 0 -> 6148 bytes web/app/components/ui/button.tsx | 56 ++++++++ web/app/components/ui/steps.tsx | 35 +++++ web/app/docs/installation/page.tsx | 33 +++++ web/app/docs/introduction/page.tsx | 25 ++++ web/app/docs/layout.tsx | 16 +++ web/app/globals.css | 60 ++++++++ web/app/guides/[slug]/page.tsx | 30 ++++ web/app/guides/page.tsx | 44 ++++++ web/app/layout.tsx | 35 +++++ web/app/page.tsx | 18 +++ web/components.json | 21 +++ web/hooks/use-mobile.tsx | 19 +++ web/hooks/use-toast.ts | 194 ++++++++++++++++++++++++++ web/lib/utils.ts | 6 + web/next.config.js | 10 +- web/package.json | 33 +++++ web/public/placeholder-logo.png | Bin 0 -> 958 bytes web/public/placeholder-logo.svg | 1 + web/public/placeholder-user.jpg | Bin 0 -> 2615 bytes web/public/placeholder.jpg | Bin 0 -> 1596 bytes web/public/placeholder.svg | 1 + web/styles/globals.css | 94 +++++++++++++ web/tailwind.config.js | 72 ++++++++++ web/tsconfig.json | 29 ++++ 37 files changed, 1221 insertions(+), 9 deletions(-) create mode 100644 .gitignore create mode 100644 web/app/components/.DS_Store create mode 100644 web/app/components/CTA.tsx create mode 100644 web/app/components/DocSidebar.tsx create mode 100644 web/app/components/Features.tsx create mode 100644 web/app/components/Footer.tsx create mode 100644 web/app/components/Header.tsx create mode 100644 web/app/components/Hero.tsx create mode 100644 web/app/components/Navbar.tsx create mode 100644 web/app/components/Pricing.tsx create mode 100644 web/app/components/ProductPreview.tsx create mode 100644 web/app/components/Testimonials.tsx create mode 100644 web/app/components/ui/.DS_Store create mode 100644 web/app/components/ui/button.tsx create mode 100644 web/app/components/ui/steps.tsx create mode 100644 web/app/docs/installation/page.tsx create mode 100644 web/app/docs/introduction/page.tsx create mode 100644 web/app/docs/layout.tsx create mode 100644 web/app/globals.css create mode 100644 web/app/guides/[slug]/page.tsx create mode 100644 web/app/guides/page.tsx create mode 100644 web/app/layout.tsx create mode 100644 web/app/page.tsx create mode 100644 web/components.json create mode 100644 web/hooks/use-mobile.tsx create mode 100644 web/hooks/use-toast.ts create mode 100644 web/lib/utils.ts create mode 100644 web/package.json create mode 100644 web/public/placeholder-logo.png create mode 100644 web/public/placeholder-logo.svg create mode 100644 web/public/placeholder-user.jpg create mode 100644 web/public/placeholder.jpg create mode 100644 web/public/placeholder.svg create mode 100644 web/styles/globals.css create mode 100644 web/tailwind.config.js create mode 100644 web/tsconfig.json diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1ec689b --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +# Next.js +web/.next/ +web/out/ + +# Node +web/node_modules/ + +# Logs +web/*.log + +# Environment variables +web/.env +web/.env.local +web/.env.development.local +web/.env.test.local +web/.env.production.local \ No newline at end of file diff --git a/web/app/components/.DS_Store b/web/app/components/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..6bade33a9ba0f24a480cbfdc854af6e16f4e2f44 GIT binary patch literal 6148 zcmeH~Jr2S!425lAKw|00n1usyg9yP1xBvncq)HtSdydZY4+Di6Rp?o=zu1k{_6e8U;9WZKn17(6`%rCU^)uq zQNCPF=aKj%RDcRhL;?Fg6u7Y_ThKop2tERU3zXfk_E`cfRsd_V1yO-%w1Uy9K89G` z+rg6iYO)2RT{MRe%{!}2F))pG(Sih~)xkgosK7{pdE}j){|ETD`G3^HlnPLRKT|*# z`{RCxm&&vC?mHt-Eh6w0<6gvL +
+

Ready to Streamline Your Workflow?

+

+ Join thousands of teams already using StreamLine to boost their productivity. +

+ +
+ + ) +} + diff --git a/web/app/components/DocSidebar.tsx b/web/app/components/DocSidebar.tsx new file mode 100644 index 0000000..a91e41d --- /dev/null +++ b/web/app/components/DocSidebar.tsx @@ -0,0 +1,36 @@ +import Link from "next/link" +import { usePathname } from "next/navigation" + +const sidebarItems = [ + { title: "Introduction", href: "/docs/introduction" }, + { title: "Installation", href: "/docs/installation" }, + { title: "Getting Started", href: "/docs/getting-started" }, + { title: "Features", href: "/docs/features" }, + { title: "API", href: "/docs/api" }, + { title: "FAQ", href: "/docs/faq" }, +] + +export default function DocSidebar() { + const pathname = usePathname() + + return ( + + ) +} + diff --git a/web/app/components/Features.tsx b/web/app/components/Features.tsx new file mode 100644 index 0000000..f37498d --- /dev/null +++ b/web/app/components/Features.tsx @@ -0,0 +1,44 @@ +import { CheckCircle, Zap, Users, TrendingUp } from "lucide-react" + +const features = [ + { + icon: , + title: "Task Management", + description: "Organize and prioritize tasks with ease.", + }, + { + icon: , + title: "Real-time Collaboration", + description: "Work together seamlessly in real-time.", + }, + { + icon: , + title: "Team Communication", + description: "Stay connected with built-in messaging.", + }, + { + icon: , + title: "Analytics Dashboard", + description: "Track progress and gain insights with powerful analytics.", + }, +] + +export default function Features() { + return ( +
+
+

Key Features

+
+ {features.map((feature, index) => ( +
+
{feature.icon}
+

{feature.title}

+

{feature.description}

+
+ ))} +
+
+
+ ) +} + diff --git a/web/app/components/Footer.tsx b/web/app/components/Footer.tsx new file mode 100644 index 0000000..3f74cc6 --- /dev/null +++ b/web/app/components/Footer.tsx @@ -0,0 +1,76 @@ +import Link from "next/link" +import { Facebook, Twitter, Instagram, Linkedin } from "lucide-react" + +export default function Footer() { + return ( +
+
+
+

StreamLine

+

Streamlining your workflow, one task at a time.

+
+
+

Product

+
    +
  • + + Features + +
  • +
  • + + Pricing + +
  • +
  • + + Integrations + +
  • +
+
+
+

Company

+
    +
  • + + About Us + +
  • +
  • + + Careers + +
  • +
  • + + Contact + +
  • +
+
+
+

Connect

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

© 2025 StreamLine. All rights reserved.

+
+
+ ) +} + diff --git a/web/app/components/Header.tsx b/web/app/components/Header.tsx new file mode 100644 index 0000000..50327b6 --- /dev/null +++ b/web/app/components/Header.tsx @@ -0,0 +1,27 @@ +import Link from "next/link" +import { Button } from "@/components/ui/button" + +export default function Header() { + return ( +
+
+ + StreamLine + + + +
+
+ ) +} + diff --git a/web/app/components/Hero.tsx b/web/app/components/Hero.tsx new file mode 100644 index 0000000..3ec585a --- /dev/null +++ b/web/app/components/Hero.tsx @@ -0,0 +1,23 @@ +import { Button } from "@/components/ui/button" + +export default function Hero() { + return ( +
+
+

+ Everything App +
+ for your teams +

+

+ Huly, an open-source platform, serves as an all-in-one replacement of Linear, Jira, Slack, and Notion. +

+ +
+
+ ) +} + diff --git a/web/app/components/Navbar.tsx b/web/app/components/Navbar.tsx new file mode 100644 index 0000000..d077eda --- /dev/null +++ b/web/app/components/Navbar.tsx @@ -0,0 +1,47 @@ +import Link from "next/link" +import { Button } from "@/components/ui/button" + +export default function Navbar() { + return ( + + ) +} + diff --git a/web/app/components/Pricing.tsx b/web/app/components/Pricing.tsx new file mode 100644 index 0000000..47bf609 --- /dev/null +++ b/web/app/components/Pricing.tsx @@ -0,0 +1,53 @@ +import { Check } from "lucide-react" +import { Button } from "@/components/ui/button" + +const plans = [ + { + name: "Basic", + price: "$9", + features: ["5 team members", "10 projects", "Basic analytics", "Email support"], + }, + { + name: "Pro", + price: "$29", + features: ["Unlimited team members", "Unlimited projects", "Advanced analytics", "Priority support"], + }, + { + name: "Enterprise", + price: "Custom", + features: ["Custom features", "Dedicated account manager", "On-premise deployment", "24/7 phone support"], + }, +] + +export default function Pricing() { + return ( +
+
+

Choose Your Plan

+
+ {plans.map((plan, index) => ( +
+

{plan.name}

+

+ {plan.price} + /month +

+
    + {plan.features.map((feature, featureIndex) => ( +
  • + + {feature} +
  • + ))} +
+ +
+ ))} +
+
+
+ ) +} + diff --git a/web/app/components/ProductPreview.tsx b/web/app/components/ProductPreview.tsx new file mode 100644 index 0000000..b127e56 --- /dev/null +++ b/web/app/components/ProductPreview.tsx @@ -0,0 +1,21 @@ +import Image from "next/image" + +export default function ProductPreview() { + return ( +
+
+
+ Huly App Interface +
+ {/* Glow effect */} +
+
+ ) +} + diff --git a/web/app/components/Testimonials.tsx b/web/app/components/Testimonials.tsx new file mode 100644 index 0000000..81fc6ac --- /dev/null +++ b/web/app/components/Testimonials.tsx @@ -0,0 +1,37 @@ +const testimonials = [ + { + quote: "StreamLine has revolutionized our team's workflow. It's a game-changer!", + author: "Jane Doe", + company: "Tech Innovators Inc.", + }, + { + quote: "The best project management tool we've ever used. Highly recommended!", + author: "John Smith", + company: "Creative Solutions LLC", + }, + { + quote: "StreamLine helped us increase productivity by 40%. It's incredible!", + author: "Emily Johnson", + company: "Startup Ventures", + }, +] + +export default function Testimonials() { + return ( +
+
+

What Our Customers Say

+
+ {testimonials.map((testimonial, index) => ( +
+

"{testimonial.quote}"

+

{testimonial.author}

+

{testimonial.company}

+
+ ))} +
+
+
+ ) +} + diff --git a/web/app/components/ui/.DS_Store b/web/app/components/ui/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..5008ddfcf53c02e82d7eee2e57c38e5672ef89f6 GIT binary patch literal 6148 zcmeH~Jr2S!425mzP>H1@V-^m;4Wg<&0T*E43hX&L&p$$qDprKhvt+--jT7}7np#A3 zem<@ulZcFPQ@L2!n>{z**++&mCkOWA81W14cNZlEfg7;MkzE(HCqgga^y>{tEnwC%0;vJ&^%eQ zLs35+`xjp>T0, + VariantProps { + asChild?: boolean +} + +const Button = React.forwardRef( + ({ className, variant, size, asChild = false, ...props }, ref) => { + const Comp = asChild ? Slot : "button" + return ( + + ) + } +) +Button.displayName = "Button" + +export { Button, buttonVariants } diff --git a/web/app/components/ui/steps.tsx b/web/app/components/ui/steps.tsx new file mode 100644 index 0000000..58c6ae9 --- /dev/null +++ b/web/app/components/ui/steps.tsx @@ -0,0 +1,35 @@ +import React from "react" + +interface StepProps { + title: string + children: React.ReactNode +} + +const Step: React.FC = ({ title, children }) => ( +
+

{title}

+ {children} +
+) + +interface StepsProps { + children: React.ReactNode +} + +const Steps: React.FC & { Step: typeof Step } = ({ children }) => ( +
+ {React.Children.map(children, (child, index) => ( +
+
+ {index + 1} +
+
{child}
+
+ ))} +
+) + +Steps.Step = Step + +export { Steps } + diff --git a/web/app/docs/installation/page.tsx b/web/app/docs/installation/page.tsx new file mode 100644 index 0000000..13d7cfe --- /dev/null +++ b/web/app/docs/installation/page.tsx @@ -0,0 +1,33 @@ +export default function InstallationPage() { + return ( +
+

Installing ProxMenux

+ +

Installation

+

To install ProxMenux, simply run the following command in your Proxmox server terminal:

+
+        
+          bash -c "$(wget -qLO - https://raw.githubusercontent.com/MacRimi/ProxMenux/main/install_proxmenux.sh)"
+        
+      
+ +

How to Use

+

+ Once installed, launch ProxMenux by running: +

+
+        menu
+      
+ +

Troubleshooting

+

+ If you encounter any issues during installation or usage, please check the{" "} + + GitHub Issues + {" "} + page or open a new issue if your problem isn't already addressed. +

+
+ ) +} + diff --git a/web/app/docs/introduction/page.tsx b/web/app/docs/introduction/page.tsx new file mode 100644 index 0000000..20b5287 --- /dev/null +++ b/web/app/docs/introduction/page.tsx @@ -0,0 +1,25 @@ +export default function IntroductionPage() { + return ( +
+

Introduction to ProxMenux

+

+ ProxMenux is a tool designed to simplify and streamline the management of Proxmox VE through a menu-based interface. It allows users to execute shell scripts in an organized way, eliminating the need to manually enter complex commands. + + It is designed for both experienced Proxmox VE administrators and less experienced users, providing a more accessible and efficient way to manage their infrastructure. + +

+

Key Features

+
    +
  • Menu-based interface for easy script execution.
  • +
  • Organized categories for quick access to available functions.
  • +
  • Scripts hosted on GitHub, always accessible and up to date.
  • +
  • Automatic text translation using Google Translate.
  • +
  • Simplified Proxmox VE management, reducing the complexity of common tasks.
  • +
+

+ The following sections of this documentation provide instructions on how to install ProxMenux and detailed explanations of each available script. +

+
+ ) +} + diff --git a/web/app/docs/layout.tsx b/web/app/docs/layout.tsx new file mode 100644 index 0000000..df5e869 --- /dev/null +++ b/web/app/docs/layout.tsx @@ -0,0 +1,16 @@ +import type React from "react" +import DocSidebar from "@/components/DocSidebar" +import Footer from "@/components/footer" + +export default function DocsLayout({ children }: { children: React.ReactNode }) { + return ( +
+
+ +
{children}
+
+
+
+ ) +} + diff --git a/web/app/globals.css b/web/app/globals.css new file mode 100644 index 0000000..1186f27 --- /dev/null +++ b/web/app/globals.css @@ -0,0 +1,60 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +@layer base { + :root { + --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: 240 5.9% 10%; + --primary-foreground: 0 0% 98%; + --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: 240 5.9% 10%; + --radius: 0.5rem; + } + + .dark { + --background: 240 10% 3.9%; + --foreground: 0 0% 98%; + --card: 240 10% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 240 10% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 240 5.9% 10%; + --secondary: 240 3.7% 15.9%; + --secondary-foreground: 0 0% 98%; + --muted: 240 3.7% 15.9%; + --muted-foreground: 240 5% 64.9%; + --accent: 240 3.7% 15.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 240 3.7% 15.9%; + --input: 240 3.7% 15.9%; + --ring: 240 4.9% 83.9%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} + diff --git a/web/app/guides/[slug]/page.tsx b/web/app/guides/[slug]/page.tsx new file mode 100644 index 0000000..6c90183 --- /dev/null +++ b/web/app/guides/[slug]/page.tsx @@ -0,0 +1,30 @@ +import fs from "fs" +import path from "path" +import { remark } from "remark" +import html from "remark-html" + +async function getGuideContent(slug: string) { + const guidePath = path.join(process.cwd(), "guides", `${slug}.md`) + const fileContents = fs.readFileSync(guidePath, "utf8") + + const result = await remark().use(html).process(fileContents) + return result.toString() +} + +export async function generateStaticParams() { + const guideFiles = fs.readdirSync(path.join(process.cwd(), "guides")) + return guideFiles.map((file) => ({ + slug: file.replace(/\.md$/, ""), + })) +} + +export default async function GuidePage({ params }: { params: { slug: string } }) { + const guideContent = await getGuideContent(params.slug) + + return ( +
+
+
+ ) +} + diff --git a/web/app/guides/page.tsx b/web/app/guides/page.tsx new file mode 100644 index 0000000..3176df6 --- /dev/null +++ b/web/app/guides/page.tsx @@ -0,0 +1,44 @@ +import Link from "next/link" + +interface Guide { + title: string + description: string + slug: string +} + +const guides: Guide[] = [ + { + title: "Setting up NVIDIA Drivers on Proxmox VE with GPU Passthrough", + description: + "Learn how to install and configure NVIDIA drivers on your Proxmox VE host and enable GPU passthrough to your virtual machines.", + slug: "nvidia_proxmox", + }, + { + title: "Ejemplo de Guía Adicional", + description: "Esta es una guía de ejemplo para mostrar cómo se manejan múltiples guías.", + slug: "example_guide", + }, + // Añade más guías aquí según sea necesario +] + +export default function GuidesPage() { + return ( +
+

ProxMenux Guides

+

Complementary guides to make the most of your Proxmox VE.

+
+ {guides.map((guide) => ( + +

{guide.title}

+

{guide.description}

+ + ))} +
+
+ ) +} + diff --git a/web/app/layout.tsx b/web/app/layout.tsx new file mode 100644 index 0000000..2d6a68b --- /dev/null +++ b/web/app/layout.tsx @@ -0,0 +1,35 @@ +import "./globals.css" +import { Inter } from "next/font/google" +import type React from "react" +import type { Metadata } from "next" +import Navbar from "@/components/navbar" +import MouseMoveEffect from "@/components/mouse-move-effect" + +const inter = Inter({ subsets: ["latin"] }) + +export const metadata: Metadata = { + title: "ProxMenux - A menu-driven script for Proxmox VE management", + description: + "ProxMenux is a tool designed to execute shell scripts in an organized manner for Proxmox VE management.", + generator: 'v0.dev' +} + +export default function RootLayout({ + children, +}: { + children: React.ReactNode +}) { + return ( + + + + +
{children}
+ + + ) +} + + + +import './globals.css' \ No newline at end of file diff --git a/web/app/page.tsx b/web/app/page.tsx new file mode 100644 index 0000000..67e5883 --- /dev/null +++ b/web/app/page.tsx @@ -0,0 +1,18 @@ +import Hero from "@/components/hero" +import Resources from "@/components/resources" +import SupportProject from "@/components/support-project" +import Footer from "@/components/footer" + +export default function Home() { + return ( +
+ {" "} + {/* Added pt-16 for navbar space */} + + + +
+
+ ) +} + diff --git a/web/components.json b/web/components.json new file mode 100644 index 0000000..d9ef0ae --- /dev/null +++ b/web/components.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": true, + "tsx": true, + "tailwind": { + "config": "tailwind.config.ts", + "css": "app/globals.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} \ No newline at end of file diff --git a/web/hooks/use-mobile.tsx b/web/hooks/use-mobile.tsx new file mode 100644 index 0000000..2b0fe1d --- /dev/null +++ b/web/hooks/use-mobile.tsx @@ -0,0 +1,19 @@ +import * as React from "react" + +const MOBILE_BREAKPOINT = 768 + +export function useIsMobile() { + const [isMobile, setIsMobile] = React.useState(undefined) + + React.useEffect(() => { + const mql = window.matchMedia(`(max-width: ${MOBILE_BREAKPOINT - 1}px)`) + const onChange = () => { + setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) + } + mql.addEventListener("change", onChange) + setIsMobile(window.innerWidth < MOBILE_BREAKPOINT) + return () => mql.removeEventListener("change", onChange) + }, []) + + return !!isMobile +} diff --git a/web/hooks/use-toast.ts b/web/hooks/use-toast.ts new file mode 100644 index 0000000..02e111d --- /dev/null +++ b/web/hooks/use-toast.ts @@ -0,0 +1,194 @@ +"use client" + +// Inspired by react-hot-toast library +import * as React from "react" + +import type { + ToastActionElement, + ToastProps, +} from "@/components/ui/toast" + +const TOAST_LIMIT = 1 +const TOAST_REMOVE_DELAY = 1000000 + +type ToasterToast = ToastProps & { + id: string + title?: React.ReactNode + description?: React.ReactNode + action?: ToastActionElement +} + +const actionTypes = { + ADD_TOAST: "ADD_TOAST", + UPDATE_TOAST: "UPDATE_TOAST", + DISMISS_TOAST: "DISMISS_TOAST", + REMOVE_TOAST: "REMOVE_TOAST", +} as const + +let count = 0 + +function genId() { + count = (count + 1) % Number.MAX_SAFE_INTEGER + return count.toString() +} + +type ActionType = typeof actionTypes + +type Action = + | { + type: ActionType["ADD_TOAST"] + toast: ToasterToast + } + | { + type: ActionType["UPDATE_TOAST"] + toast: Partial + } + | { + type: ActionType["DISMISS_TOAST"] + toastId?: ToasterToast["id"] + } + | { + type: ActionType["REMOVE_TOAST"] + toastId?: ToasterToast["id"] + } + +interface State { + toasts: ToasterToast[] +} + +const toastTimeouts = new Map>() + +const addToRemoveQueue = (toastId: string) => { + if (toastTimeouts.has(toastId)) { + return + } + + const timeout = setTimeout(() => { + toastTimeouts.delete(toastId) + dispatch({ + type: "REMOVE_TOAST", + toastId: toastId, + }) + }, TOAST_REMOVE_DELAY) + + toastTimeouts.set(toastId, timeout) +} + +export const reducer = (state: State, action: Action): State => { + switch (action.type) { + case "ADD_TOAST": + return { + ...state, + toasts: [action.toast, ...state.toasts].slice(0, TOAST_LIMIT), + } + + case "UPDATE_TOAST": + return { + ...state, + toasts: state.toasts.map((t) => + t.id === action.toast.id ? { ...t, ...action.toast } : t + ), + } + + case "DISMISS_TOAST": { + const { toastId } = action + + // ! Side effects ! - This could be extracted into a dismissToast() action, + // but I'll keep it here for simplicity + if (toastId) { + addToRemoveQueue(toastId) + } else { + state.toasts.forEach((toast) => { + addToRemoveQueue(toast.id) + }) + } + + return { + ...state, + toasts: state.toasts.map((t) => + t.id === toastId || toastId === undefined + ? { + ...t, + open: false, + } + : t + ), + } + } + case "REMOVE_TOAST": + if (action.toastId === undefined) { + return { + ...state, + toasts: [], + } + } + return { + ...state, + toasts: state.toasts.filter((t) => t.id !== action.toastId), + } + } +} + +const listeners: Array<(state: State) => void> = [] + +let memoryState: State = { toasts: [] } + +function dispatch(action: Action) { + memoryState = reducer(memoryState, action) + listeners.forEach((listener) => { + listener(memoryState) + }) +} + +type Toast = Omit + +function toast({ ...props }: Toast) { + const id = genId() + + const update = (props: ToasterToast) => + dispatch({ + type: "UPDATE_TOAST", + toast: { ...props, id }, + }) + const dismiss = () => dispatch({ type: "DISMISS_TOAST", toastId: id }) + + dispatch({ + type: "ADD_TOAST", + toast: { + ...props, + id, + open: true, + onOpenChange: (open) => { + if (!open) dismiss() + }, + }, + }) + + return { + id: id, + dismiss, + update, + } +} + +function useToast() { + const [state, setState] = React.useState(memoryState) + + React.useEffect(() => { + listeners.push(setState) + return () => { + const index = listeners.indexOf(setState) + if (index > -1) { + listeners.splice(index, 1) + } + } + }, [state]) + + return { + ...state, + toast, + dismiss: (toastId?: string) => dispatch({ type: "DISMISS_TOAST", toastId }), + } +} + +export { useToast, toast } diff --git a/web/lib/utils.ts b/web/lib/utils.ts new file mode 100644 index 0000000..bd0c391 --- /dev/null +++ b/web/lib/utils.ts @@ -0,0 +1,6 @@ +import { clsx, type ClassValue } from "clsx" +import { twMerge } from "tailwind-merge" + +export function cn(...inputs: ClassValue[]) { + return twMerge(clsx(inputs)) +} diff --git a/web/next.config.js b/web/next.config.js index f5d4549..e272422 100644 --- a/web/next.config.js +++ b/web/next.config.js @@ -7,15 +7,7 @@ const nextConfig = { assetPrefix: "/ProxMenux/", basePath: "/ProxMenux", distDir: "out", - webpack: (config, { isServer }) => { - if (!isServer) { - config.resolve.fallback = { - ...config.resolve.fallback, - fs: false, - } - } - return config - }, } module.exports = nextConfig + diff --git a/web/package.json b/web/package.json new file mode 100644 index 0000000..a1965e1 --- /dev/null +++ b/web/package.json @@ -0,0 +1,33 @@ +{ + "name": "proxmenux-docs", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "next": "13.4.7", + "react": "18.2.0", + "react-dom": "18.2.0", + "remark": "^14.0.3", + "remark-html": "^15.0.2", + "@radix-ui/react-slot": "^1.0.2", + "class-variance-authority": "^0.7.0", + "clsx": "^2.0.0", + "lucide-react": "^0.284.0", + "tailwind-merge": "^1.14.0", + "tailwindcss-animate": "^1.0.7" + }, + "devDependencies": { + "@types/node": "^20.8.4", + "@types/react": "^18.2.27", + "autoprefixer": "^10.4.16", + "postcss": "^8.4.31", + "tailwindcss": "^3.3.3", + "typescript": "^5.2.2" + } +} + diff --git a/web/public/placeholder-logo.png b/web/public/placeholder-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..6528839b3f22f0acf7a0e8ab46d267d2f923369e GIT binary patch literal 958 zcmb7@O-~bH6osdyMJa<;J_IU_rBI6vAuR<7Hl)_{gQ?Kewkj&LMPlMYf{P~A1a-l} zALGWQ32E3gaqCVNCjJEeg3s-pjNKU~_ndp~dHa6MJGU2SopPx#;tTi;!x*tPvlYW| zA&rZ(j%R>8v5g{-@$yzJCu5g+UZiJ+`Tg})rBszgL*N1MgJwDYj-=5x*%@hNGqv|> zop*}K$NoWzwWvA~(s3FJ-Gl^$D+hVrdf_#7CP@oBP!@R9bGY4fB;qBAwfY^Iqj4zG z_3^e=D=9ODn#}e+jWL`?_ZvJq4b1}o2T1m4fct(Esa~@slW{_ zm6KDk)&9Cjk#MUccUJ@MacD5_w$vWdw4o{a`y_x`{sg`92GxY)U>W!i%8hCq&AvulJJhOsT0z*iua#615XH0+mgY845R&u&3=?At1ZwVcw9mOG~LF(*Njm1zTGp#y`NuUQA8W;gFjzlaBuGDb2-cb i&}c^U_C`U+`>&`QtI|Y^Ku#W!Kc1B \ No newline at end of file diff --git a/web/public/placeholder-user.jpg b/web/public/placeholder-user.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6faa819ce703cf0363a8f624b7cee4ecdc8ba049 GIT binary patch literal 2615 zcmb_eTT@$A6y7I+TnOakP#~rd$c4~wD@X_>GJ!*ZAQW0|2Ex4{mOBL0S_-YsXh+*= zopJQV8J~P~^uh7+Jel zZw1Nk|L|tu6^u?n8rek~`sOwWXoWlrIS=7z8Wy91)lbzOiTSvE7zW|W zbYPCd(s`uRJh(?&RTXN8XYh_HEXjm>LAv~TeOt}!$DCrwRL8amLeudaI<02*pYFjx zhzJ;H-X^c*Y})G+RFBDmGIA)UU2tK}qp9S0l8?-S=yD0gY*KO^R=b_oL1}YDu0`t{ z9>Ei=g(YtP)B1B4VFQg#W0_34ilQxx+q)d`1%j1Bqih>2Z0HW9Lv}u`$EahKZ#XE2 zn~*j3Y_{%98nJS=V&3G-uxKr+4zRIXqE*JUz43c*FJo9=t4|gc!Dxawhk7ZO*(k%H zC#88}ZuStKjM(ymm!0Nkqp-Uihee4+jyJ5F4fR@@pbO1Wob`_{5Dbq#A*(F-kJi-F4JOcu*on!qk7G3O+vnXe$I*T{= z?|lL5VEXS*-{81P^L&sOc+5bf#nbwXI*J?aIxB{h@weWv01lBNnY*?==>HA$RxSlNCQk ze}48_FQto0Itu}8G~-f)o|Oo(3+#@~fKLoj%7AK!Yy(&hoeov={q2oks0ES(DY_q> zyS3mw2Z%wfX4{|_sXxXZYD9Uv!3q|FXg4x5umBI_?oEFbimt8nG-yx<0i`-cXRhpJ z^2GtQvuz2MESO5u1?BeSY%Wg#dyeIWmW@)g0bvNOC^Y$|3|!#=*s=&(vC>U1?=lW; zLAAxF}bm+6|;|iO@JTYr!P{^ z0~c25Cb7y8`v#$YeJ9ZabrllPCQ6`Wu_;NE4z~|95~*l_x(3OHTwBmXFsL zDt(rAnT=HTz_6)lCOhaUXa0JRt}dW@jnZ+M2h(&&`8H*qGV#9#DZ7*ofDTSK^Hx}v zBZ5Kl5-1y1x?ZR|fRGbJy)ctLcpmEd+Vos1Dl1KCA3WAIa129$C!C(#kD7P^urrt1 liqSwtG9Zeytp9`_{a|KdHpQgi$FA%;yZ&P?{;SH&{Rcu@f8qcD literal 0 HcmV?d00001 diff --git a/web/public/placeholder.jpg b/web/public/placeholder.jpg new file mode 100644 index 0000000000000000000000000000000000000000..a6bf2ee648dabcdbf73944f3a9a4d77962e777bc GIT binary patch literal 1596 zcmdT^TTc@~6h5=JZd)35${jIup=G%Q(o%t9LMScU3lVL(sXTEP^Z{xC(Fcu*J{y06 zKg9UpKk&_8fj_}-cGeb2OyrT-bDceB&dfP8=knXv%RfNYQlVTR#)wMzNd6Gay*N4` zT3)76A~H#5QBol>e8hT5_hh0acBdh)uwmau@m4w_&9~YF7XuN5zt2^KVa-*cDGd(> zO>Jtz88ZONR1S|%kL!oWC$aQIk}|pSvL@nScyhuMEtuW~{`@5TG++=YA@^2t;-)x{ z$~Y;Usa(VaP(L=QGjDin5vhvCbTR^FFckKUz%2OUkE$73z|c&i&Jpvenwh;{Dla{L@^tgr;nDL~7q8z)qgzC& zA2&tT1k@{w$hM|mFA8d@pe(AQ>WZ#tqVf#oRjsJ4#Zn7xdU9a0+MYGigWI`|+KNbi zB`y;oiqRR^nOc=BEwHaZ-4obk!*%y@Dx={T7O zvvYa3R4$E8KU#lSSj!emt_(?e))I;ER&Gb5wmacWxHlgDLM^ISdXD#unHT=7iTlHJ zX);W=HlwB#1tLv2~9i5h}$U((POn=pWlCJcuV0wf{LU2dxQ^mArAl7 J1mawo_zkn`?qmP} literal 0 HcmV?d00001 diff --git a/web/public/placeholder.svg b/web/public/placeholder.svg new file mode 100644 index 0000000..e763910 --- /dev/null +++ b/web/public/placeholder.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/web/styles/globals.css b/web/styles/globals.css new file mode 100644 index 0000000..ac68442 --- /dev/null +++ b/web/styles/globals.css @@ -0,0 +1,94 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +body { + font-family: Arial, Helvetica, sans-serif; +} + +@layer utilities { + .text-balance { + text-wrap: balance; + } +} + +@layer base { + :root { + --background: 0 0% 100%; + --foreground: 0 0% 3.9%; + --card: 0 0% 100%; + --card-foreground: 0 0% 3.9%; + --popover: 0 0% 100%; + --popover-foreground: 0 0% 3.9%; + --primary: 0 0% 9%; + --primary-foreground: 0 0% 98%; + --secondary: 0 0% 96.1%; + --secondary-foreground: 0 0% 9%; + --muted: 0 0% 96.1%; + --muted-foreground: 0 0% 45.1%; + --accent: 0 0% 96.1%; + --accent-foreground: 0 0% 9%; + --destructive: 0 84.2% 60.2%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 89.8%; + --input: 0 0% 89.8%; + --ring: 0 0% 3.9%; + --chart-1: 12 76% 61%; + --chart-2: 173 58% 39%; + --chart-3: 197 37% 24%; + --chart-4: 43 74% 66%; + --chart-5: 27 87% 67%; + --radius: 0.5rem; + --sidebar-background: 0 0% 98%; + --sidebar-foreground: 240 5.3% 26.1%; + --sidebar-primary: 240 5.9% 10%; + --sidebar-primary-foreground: 0 0% 98%; + --sidebar-accent: 240 4.8% 95.9%; + --sidebar-accent-foreground: 240 5.9% 10%; + --sidebar-border: 220 13% 91%; + --sidebar-ring: 217.2 91.2% 59.8%; + } + .dark { + --background: 0 0% 3.9%; + --foreground: 0 0% 98%; + --card: 0 0% 3.9%; + --card-foreground: 0 0% 98%; + --popover: 0 0% 3.9%; + --popover-foreground: 0 0% 98%; + --primary: 0 0% 98%; + --primary-foreground: 0 0% 9%; + --secondary: 0 0% 14.9%; + --secondary-foreground: 0 0% 98%; + --muted: 0 0% 14.9%; + --muted-foreground: 0 0% 63.9%; + --accent: 0 0% 14.9%; + --accent-foreground: 0 0% 98%; + --destructive: 0 62.8% 30.6%; + --destructive-foreground: 0 0% 98%; + --border: 0 0% 14.9%; + --input: 0 0% 14.9%; + --ring: 0 0% 83.1%; + --chart-1: 220 70% 50%; + --chart-2: 160 60% 45%; + --chart-3: 30 80% 55%; + --chart-4: 280 65% 60%; + --chart-5: 340 75% 55%; + --sidebar-background: 240 5.9% 10%; + --sidebar-foreground: 240 4.8% 95.9%; + --sidebar-primary: 224.3 76.3% 48%; + --sidebar-primary-foreground: 0 0% 100%; + --sidebar-accent: 240 3.7% 15.9%; + --sidebar-accent-foreground: 240 4.8% 95.9%; + --sidebar-border: 240 3.7% 15.9%; + --sidebar-ring: 217.2 91.2% 59.8%; + } +} + +@layer base { + * { + @apply border-border; + } + body { + @apply bg-background text-foreground; + } +} diff --git a/web/tailwind.config.js b/web/tailwind.config.js new file mode 100644 index 0000000..2ae83ea --- /dev/null +++ b/web/tailwind.config.js @@ -0,0 +1,72 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + darkMode: ["class"], + content: ["./app/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}", "./pages/**/*.{ts,tsx}"], + theme: { + container: { + center: true, + padding: "2rem", + screens: { + "2xl": "1400px", + }, + }, + extend: { + colors: { + border: "hsl(var(--border))", + input: "hsl(var(--input))", + ring: "hsl(var(--ring))", + background: "hsl(var(--background))", + foreground: "hsl(var(--foreground))", + primary: { + DEFAULT: "hsl(var(--primary))", + foreground: "hsl(var(--primary-foreground))", + }, + secondary: { + DEFAULT: "hsl(var(--secondary))", + foreground: "hsl(var(--secondary-foreground))", + }, + destructive: { + DEFAULT: "hsl(var(--destructive))", + foreground: "hsl(var(--destructive-foreground))", + }, + muted: { + DEFAULT: "hsl(var(--muted))", + foreground: "hsl(var(--muted-foreground))", + }, + accent: { + DEFAULT: "hsl(var(--accent))", + foreground: "hsl(var(--accent-foreground))", + }, + popover: { + DEFAULT: "hsl(var(--popover))", + foreground: "hsl(var(--popover-foreground))", + }, + card: { + DEFAULT: "hsl(var(--card))", + foreground: "hsl(var(--card-foreground))", + }, + }, + borderRadius: { + lg: "var(--radius)", + md: "calc(var(--radius) - 2px)", + sm: "calc(var(--radius) - 4px)", + }, + keyframes: { + "accordion-down": { + from: { height: 0 }, + to: { height: "var(--radix-accordion-content-height)" }, + }, + "accordion-up": { + from: { height: "var(--radix-accordion-content-height)" }, + to: { height: 0 }, + }, + }, + animation: { + "accordion-down": "accordion-down 0.2s ease-out", + "accordion-up": "accordion-up 0.2s ease-out", + }, + }, + }, + plugins: [require("tailwindcss-animate")], +} + diff --git a/web/tsconfig.json b/web/tsconfig.json new file mode 100644 index 0000000..364f802 --- /dev/null +++ b/web/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} +