From 128762c5ce2fafdcaae91491c9016f296c832f9e Mon Sep 17 00:00:00 2001 From: nishanth-kj Date: Mon, 2 Mar 2026 21:00:09 +0530 Subject: [PATCH 01/18] convert --- app/convert/epoch/page.tsx | 18 ++ components/convert/epoch-converter.tsx | 340 +++++++++++++++++++++++++ 2 files changed, 358 insertions(+) create mode 100644 app/convert/epoch/page.tsx create mode 100644 components/convert/epoch-converter.tsx diff --git a/app/convert/epoch/page.tsx b/app/convert/epoch/page.tsx new file mode 100644 index 0000000..47d3b53 --- /dev/null +++ b/app/convert/epoch/page.tsx @@ -0,0 +1,18 @@ +"use client"; + +import { SidebarProvider } from "@/components/ui/sidebar"; +import { AppSidebar } from "@/components/layout/app-sidebar"; +import { EpochConverter } from "@/components/convert/epoch-converter"; + +export default function EpochConverterPage() { + return ( + +
+ +
+ +
+
+
+ ); +} diff --git a/components/convert/epoch-converter.tsx b/components/convert/epoch-converter.tsx new file mode 100644 index 0000000..f338158 --- /dev/null +++ b/components/convert/epoch-converter.tsx @@ -0,0 +1,340 @@ +"use client"; + +import React, { useState, useEffect, useCallback } from "react"; +import { + Clock, + Copy, + Check, + ArrowRight, + RefreshCw, + Calendar, + Globe, + Timer, + Zap, +} from "lucide-react"; +import { Button } from "@/components/ui/button"; + +function formatRelativeTime(date: Date): string { + const now = new Date(); + const diffMs = now.getTime() - date.getTime(); + const absDiff = Math.abs(diffMs); + const isFuture = diffMs < 0; + const prefix = isFuture ? "in " : ""; + const suffix = isFuture ? "" : " ago"; + + const seconds = Math.floor(absDiff / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); + const months = Math.floor(days / 30); + const years = Math.floor(days / 365); + + if (seconds < 60) return `${prefix}${seconds} second${seconds !== 1 ? "s" : ""}${suffix}`; + if (minutes < 60) return `${prefix}${minutes} minute${minutes !== 1 ? "s" : ""}${suffix}`; + if (hours < 24) return `${prefix}${hours} hour${hours !== 1 ? "s" : ""}${suffix}`; + if (days < 30) return `${prefix}${days} day${days !== 1 ? "s" : ""}${suffix}`; + if (months < 12) return `${prefix}${months} month${months !== 1 ? "s" : ""}${suffix}`; + return `${prefix}${years} year${years !== 1 ? "s" : ""}${suffix}`; +} + +function toLocalDatetimeString(date: Date): string { + const y = date.getFullYear(); + const m = String(date.getMonth() + 1).padStart(2, "0"); + const d = String(date.getDate()).padStart(2, "0"); + const h = String(date.getHours()).padStart(2, "0"); + const min = String(date.getMinutes()).padStart(2, "0"); + const s = String(date.getSeconds()).padStart(2, "0"); + return `${y}-${m}-${d}T${h}:${min}:${s}`; +} + +type CopiedField = string | null; + +export function EpochConverter() { + // Epoch -> Date + const [epochInput, setEpochInput] = useState(""); + const [parsedDate, setParsedDate] = useState(null); + const [epochError, setEpochError] = useState(""); + const [isMillis, setIsMillis] = useState(false); + + // Date -> Epoch + const [dateInput, setDateInput] = useState(""); + const [dateEpochSeconds, setDateEpochSeconds] = useState(null); + const [dateEpochMillis, setDateEpochMillis] = useState(null); + + // Live clock + const [liveEpoch, setLiveEpoch] = useState(Math.floor(Date.now() / 1000)); + + // Copy feedback + const [copied, setCopied] = useState(null); + + useEffect(() => { + const interval = setInterval(() => { + setLiveEpoch(Math.floor(Date.now() / 1000)); + }, 1000); + return () => clearInterval(interval); + }, []); + + const copyToClipboard = useCallback((text: string, field: string) => { + navigator.clipboard.writeText(text); + setCopied(field); + setTimeout(() => setCopied(null), 1500); + }, []); + + // Parse epoch input + useEffect(() => { + if (!epochInput.trim()) { + setParsedDate(null); + setEpochError(""); + return; + } + const num = Number(epochInput.trim()); + if (isNaN(num)) { + setEpochError("Enter a valid number"); + setParsedDate(null); + return; + } + // Auto-detect: if > 10 digits, treat as milliseconds + const isMs = epochInput.trim().length > 10; + setIsMillis(isMs); + const ms = isMs ? num : num * 1000; + const d = new Date(ms); + if (isNaN(d.getTime())) { + setEpochError("Invalid timestamp"); + setParsedDate(null); + return; + } + setEpochError(""); + setParsedDate(d); + }, [epochInput]); + + // Parse date input + useEffect(() => { + if (!dateInput) { + setDateEpochSeconds(null); + setDateEpochMillis(null); + return; + } + const d = new Date(dateInput); + if (isNaN(d.getTime())) { + setDateEpochSeconds(null); + setDateEpochMillis(null); + return; + } + setDateEpochSeconds(Math.floor(d.getTime() / 1000)); + setDateEpochMillis(d.getTime()); + }, [dateInput]); + + const setNow = () => { + const now = Math.floor(Date.now() / 1000); + setEpochInput(String(now)); + }; + + const setDateNow = () => { + setDateInput(toLocalDatetimeString(new Date())); + }; + + const CopyButton = ({ value, field }: { value: string; field: string }) => ( + + ); + + return ( +
+ {/* Top Bar — live epoch */} +
+
+
+ + Epoch Converter +
+
+
+
+ + {liveEpoch} +
+ +
+
+ + {/* Main Content — Two Cards */} +
+
+ + {/* Card 1: Epoch -> Date */} +
+ {/* Card Header */} +
+
+ +
+
+

Epoch to Date

+

Convert Unix timestamp to human-readable date

+
+
+ + {/* Input */} +
+
+
+ setEpochInput(e.target.value)} + placeholder="e.g. 1740000000" + className="w-full h-11 px-4 rounded-xl border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-800/50 text-sm font-mono focus:outline-none focus:ring-2 focus:ring-indigo-500/30 focus:border-indigo-500 dark:focus:border-indigo-500 transition-all placeholder:text-zinc-400" + /> + {isMillis && epochInput && !epochError && ( + ms + )} +
+ +
+ {epochError && ( +

{epochError}

+ )} + + {/* Results */} + {parsedDate && ( +
+ {[ + { label: "UTC", value: parsedDate.toUTCString(), field: "utc", icon: Globe }, + { label: "Local", value: parsedDate.toLocaleString(), field: "local", icon: Calendar }, + { label: "ISO 8601", value: parsedDate.toISOString(), field: "iso", icon: Clock }, + { label: "Relative", value: formatRelativeTime(parsedDate), field: "relative", icon: Timer }, + ].map((item) => ( +
+
+ + {item.label} + {item.value} +
+ +
+ ))} +
+ )} + + {!parsedDate && !epochError && !epochInput && ( +
+ +

Enter a timestamp above

+
+ )} +
+
+ + {/* Card 2: Date -> Epoch */} +
+ {/* Card Header */} +
+
+ +
+
+

Date to Epoch

+

Convert date and time to Unix timestamp

+
+
+ + {/* Input */} +
+
+ setDateInput(e.target.value)} + className="flex-1 h-11 px-4 rounded-xl border border-zinc-200 dark:border-zinc-700 bg-zinc-50 dark:bg-zinc-800/50 text-sm font-mono focus:outline-none focus:ring-2 focus:ring-purple-500/30 focus:border-purple-500 dark:focus:border-purple-500 transition-all [color-scheme:light] dark:[color-scheme:dark]" + /> + +
+ + {/* Results */} + {dateEpochSeconds !== null && ( +
+ {[ + { label: "Seconds", value: String(dateEpochSeconds), field: "d-sec" }, + { label: "Millis", value: String(dateEpochMillis), field: "d-ms" }, + ].map((item) => ( +
+
+ + {item.label} + {item.value} +
+ +
+ ))} + + {/* Extra info */} +
+ {[ + { label: "UTC", value: new Date(dateEpochSeconds * 1000).toUTCString(), field: "d-utc", icon: Globe }, + { label: "ISO 8601", value: new Date(dateEpochSeconds * 1000).toISOString(), field: "d-iso", icon: Clock }, + { label: "Relative", value: formatRelativeTime(new Date(dateEpochSeconds * 1000)), field: "d-rel", icon: ArrowRight }, + ].map((item) => ( +
+
+ + {item.label} + {item.value} +
+ +
+ ))} +
+
+ )} + + {dateEpochSeconds === null && ( +
+ +

Select a date above

+
+ )} +
+
+
+
+
+ ); +} From 94f809d002138811ab3db2401cad327665c7232d Mon Sep 17 00:00:00 2001 From: nishanth-kj Date: Mon, 2 Mar 2026 21:29:22 +0530 Subject: [PATCH 02/18] added in navbar --- components/layout/app-sidebar.tsx | 23 ++++++++++++++++++++++- components/layout/navbar.tsx | 8 +++++--- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/components/layout/app-sidebar.tsx b/components/layout/app-sidebar.tsx index 2c138bc..f90be1d 100644 --- a/components/layout/app-sidebar.tsx +++ b/components/layout/app-sidebar.tsx @@ -13,7 +13,8 @@ import { FileCode, StickyNote, Table, - Image + Image, + Clock } from "lucide-react"; import { @@ -86,6 +87,26 @@ export function AppSidebar() { + + Convert + + + {[ + { id: "epoch", label: "Epoch Converter", icon: Clock, tooltip: "Epoch Converter" }, + ].map((item) => ( + + + + + {item.label} + + + + ))} + + + + diff --git a/components/layout/navbar.tsx b/components/layout/navbar.tsx index 377e02b..d80b199 100644 --- a/components/layout/navbar.tsx +++ b/components/layout/navbar.tsx @@ -1,7 +1,7 @@ "use client"; import React, { useState } from 'react'; -import { Moon, Sun, Layout, Github, Menu, X, FileEdit, Code2, Home } from 'lucide-react'; +import { Moon, Sun, Layout, Github, Menu, X, FileEdit, Code2, Home, ArrowLeftRight } from 'lucide-react'; import { useTheme } from 'next-themes'; import { Button } from '@/components/ui/button'; import Link from 'next/link'; @@ -19,13 +19,15 @@ export function Navbar() { const tools = [ { id: "editor", label: "Editor", href: "/editor", icon: FileEdit }, { id: "viewer", label: "Viewer", href: "/view", icon: Layout }, - { id: "ide", label: "IDE", href: "/ide", icon: Code2 } + { id: "ide", label: "IDE", href: "/ide", icon: Code2 }, + { id: "convert", label: "Convert", href: "/convert/epoch", icon: ArrowLeftRight } ]; const activeTool = pathname.includes("editor") ? "editor" : pathname.includes("ide") ? "ide" : pathname.includes("view") ? "viewer" - : null; + : pathname.includes("convert") ? "convert" + : null; return ( <> From 1c12a93f3208683ac0d7dae9e72791eaffb59993 Mon Sep 17 00:00:00 2001 From: Nishanth Date: Tue, 14 Apr 2026 23:26:32 +0530 Subject: [PATCH 03/18] scaffold project architecture with new IDE, converter, and viewer modules, and initialize WASM support --- .github/workflows/deploy.yml | 65 +++ app/convert/page.tsx | 5 + app/docs/page.tsx | 5 + app/ide/page.tsx | 56 +- app/page.tsx | 148 ++++- app/view/page.tsx | 21 +- .../convert/ConvertPage.tsx | 4 +- components/convert/epoch-converter.tsx | 54 +- components/convert/index.ts | 1 + components/docs/DocsPage.tsx | 130 +++++ components/docs/index.ts | 1 + components/editor/EditorPage.tsx | 17 + components/editor/index.ts | 1 + components/features/json/format.ts | 33 ++ components/features/time/epoch.ts | 33 ++ components/features/time/timezone.ts | 20 + components/ide/IDEPage.tsx | 59 ++ components/ide/index.ts | 1 + components/json/tree-viewer.tsx | 124 ++++ components/layout/client-layout.tsx | 38 ++ components/layout/command-menu.tsx | 75 +++ components/layout/ide-workspace.tsx | 269 +++++++++ components/layout/splash-screen.tsx | 88 +++ components/layout/tool-shell.tsx | 86 +++ components/shared/ads-card.tsx | 56 ++ components/view/ViewPage.tsx | 22 + components/view/index.ts | 1 + lib/constants/tools.ts | 110 ++++ next.config.ts | 5 +- package-lock.json | 540 +++++++++++------- package.json | 16 +- types/index.ts | 2 +- types/{ => props}/container.ts | 2 +- wasm/Cargo.toml | 10 + wasm/loader.ts | 0 wasm/src/lib.rs | 11 + 36 files changed, 1779 insertions(+), 330 deletions(-) create mode 100644 .github/workflows/deploy.yml create mode 100644 app/convert/page.tsx create mode 100644 app/docs/page.tsx rename app/convert/epoch/page.tsx => components/convert/ConvertPage.tsx (80%) create mode 100644 components/convert/index.ts create mode 100644 components/docs/DocsPage.tsx create mode 100644 components/docs/index.ts create mode 100644 components/editor/EditorPage.tsx create mode 100644 components/editor/index.ts create mode 100644 components/features/json/format.ts create mode 100644 components/features/time/epoch.ts create mode 100644 components/features/time/timezone.ts create mode 100644 components/ide/IDEPage.tsx create mode 100644 components/ide/index.ts create mode 100644 components/json/tree-viewer.tsx create mode 100644 components/layout/client-layout.tsx create mode 100644 components/layout/command-menu.tsx create mode 100644 components/layout/ide-workspace.tsx create mode 100644 components/layout/splash-screen.tsx create mode 100644 components/layout/tool-shell.tsx create mode 100644 components/shared/ads-card.tsx create mode 100644 components/view/ViewPage.tsx create mode 100644 components/view/index.ts create mode 100644 lib/constants/tools.ts rename types/{ => props}/container.ts (71%) create mode 100644 wasm/Cargo.toml create mode 100644 wasm/loader.ts create mode 100644 wasm/src/lib.rs diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..e96179b --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,65 @@ +name: Deploy to GitHub Pages + +on: + push: + branches: ["**"] + pull_request: + branches: ["**"] + +permissions: + contents: read + pages: write + id-token: write + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Install Rust + uses: dtolnay/rust-toolchain@stable + with: + targets: wasm32-unknown-unknown + + - name: Install wasm-pack + run: curl https://rustwasm.github.io/wasm-pack/installer/init.sh -sSf | sh + + - name: Build WASM + run: | + cd wasm + wasm-pack build --target web + + - name: Setup Node + uses: actions/setup-node@v4 + with: + node-version: "20" + cache: "npm" + + - name: Install dependencies + run: npm i -f + + - name: Lint + run: npm run lint + + - name: Build Next.js + run: npm run build + + - name: Upload artifact + if: github.event_name == 'push' + uses: actions/upload-pages-artifact@v3 + with: + path: ./out + + deploy: + if: github.event_name == 'push' + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + runs-on: ubuntu-latest + needs: build + steps: + - name: Deploy to GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 diff --git a/app/convert/page.tsx b/app/convert/page.tsx new file mode 100644 index 0000000..51825d5 --- /dev/null +++ b/app/convert/page.tsx @@ -0,0 +1,5 @@ +import { ConvertPage } from "@/components/convert"; + +export default function Page() { + return ; +} diff --git a/app/docs/page.tsx b/app/docs/page.tsx new file mode 100644 index 0000000..4a0351f --- /dev/null +++ b/app/docs/page.tsx @@ -0,0 +1,5 @@ +import { DocsPage } from "@/components/docs"; + +export default function Page() { + return ; +} diff --git a/app/ide/page.tsx b/app/ide/page.tsx index 165d23b..d909615 100644 --- a/app/ide/page.tsx +++ b/app/ide/page.tsx @@ -1,59 +1,11 @@ "use client"; -import React from 'react'; -import { Blocks, Rocket, Hammer, Construction } from 'lucide-react'; +import { IdeWorkspace } from "@/components/layout/ide-workspace"; -export default function IDEPage() { +export default function IdePage() { return ( -
- {/* Background Decorative Elements */} -
-
- -
-
- - Under Development -
- -

- The Next-Gen
- Cloud IDE -

- -

- Building a browser-based client WASM type build IDE with real-time collaboration, - WASM-powered runtimes, and deep integration. -

- -
-
- -

Fast Runtime

-

WASM Powered

-
-
- -

Extensions

-

Rich Ecosystem

-
-
- -

Cloud Build

-

Instant Deploy

-
-
- -
- - -
-
- +
+
); } diff --git a/app/page.tsx b/app/page.tsx index 92943c4..416a960 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,15 +1,139 @@ "use client"; -import { Hero } from "@/components/landing/hero"; -import { Features } from "@/components/landing/features"; -import { Footer } from "@/components/landing/footer"; - -export default function Home() { - return ( -
- - -
-
- ); +import React, { useState } from 'react'; +import { + Search, + Filter, + LayoutGrid, +} from 'lucide-react'; +import { Button } from '@/components/ui/button'; +import { Input } from '@/components/ui/input'; +import { AdsCard } from '@/components/shared/ads-card'; +import gsap from 'gsap'; +import { useGSAP } from '@gsap/react'; +import { TOOLS, TOOL_CATEGORIES, type Tool, type Category } from '@/lib/constants/tools'; + +export default function ToolsListingPage() { + const container = React.useRef(null); + + useGSAP(() => { + gsap.from(".tool-card", { + y: 40, + opacity: 0, + duration: 0.8, + stagger: 0.1, + ease: "power4.out", + delay: 0.2 + }); + + gsap.from(".category-btn", { + x: -20, + opacity: 0, + duration: 0.5, + stagger: 0.05, + ease: "back.out(1.7)" + }); + }, { scope: container }); + + const [searchQuery, setSearchQuery] = useState(""); + const [activeCategory, setActiveCategory] = useState("all"); + + const filteredTools = TOOLS.filter((tool: Tool) => { + const matchesSearch = tool.name.toLowerCase().includes(searchQuery.toLowerCase()) || + tool.description.toLowerCase().includes(searchQuery.toLowerCase()); + const matchesCategory = activeCategory === 'all' || tool.category === activeCategory; + return matchesSearch && matchesCategory; + }); + + return ( +
+
+ {/* Header */} +
+
+

+ All Tools +

+

+ Explore our comprehensive suite of developer utilities and converters. +

+
+
+ + {/* Search and Filters */} +
+
+ + setSearchQuery(e.target.value)} + /> +
+
+ + +
+
+ + {/* Categories */} +
+ + {TOOL_CATEGORIES.map((cat: Category) => ( + + ))} +
+ + {/* Tools Grid */} +
+ {filteredTools.map((tool: Tool) => ( +
window.open(tool.href, '_self')} + > +
+
+ +
+
+

{tool.name}

+

{tool.description}

+
+
+ + {tool.status} + +
+
+
+ ))} + +
+ +
+
+ + +
+
+ ); } diff --git a/app/view/page.tsx b/app/view/page.tsx index c93f5bf..5fba31b 100644 --- a/app/view/page.tsx +++ b/app/view/page.tsx @@ -1,22 +1,13 @@ -"use client"; - -import { SidebarProvider } from "@/components/ui/sidebar"; -import { AppSidebar } from "@/components/layout/app-sidebar"; import { ViewerContainer } from "@/components/view/viewer-container"; import { DEFAULT_CONTENT } from "@/data/default-content"; export default function ViewPage() { return ( - -
- -
- -
-
-
+
+ +
); } diff --git a/app/convert/epoch/page.tsx b/components/convert/ConvertPage.tsx similarity index 80% rename from app/convert/epoch/page.tsx rename to components/convert/ConvertPage.tsx index 47d3b53..3a9d76c 100644 --- a/app/convert/epoch/page.tsx +++ b/components/convert/ConvertPage.tsx @@ -2,9 +2,9 @@ import { SidebarProvider } from "@/components/ui/sidebar"; import { AppSidebar } from "@/components/layout/app-sidebar"; -import { EpochConverter } from "@/components/convert/epoch-converter"; +import { EpochConverter } from "./epoch-converter"; -export default function EpochConverterPage() { +export function ConvertPage() { return (
diff --git a/components/convert/epoch-converter.tsx b/components/convert/epoch-converter.tsx index f338158..fd46eee 100644 --- a/components/convert/epoch-converter.tsx +++ b/components/convert/epoch-converter.tsx @@ -50,23 +50,13 @@ function toLocalDatetimeString(date: Date): string { type CopiedField = string | null; export function EpochConverter() { - // Epoch -> Date + // State const [epochInput, setEpochInput] = useState(""); - const [parsedDate, setParsedDate] = useState(null); - const [epochError, setEpochError] = useState(""); - const [isMillis, setIsMillis] = useState(false); - - // Date -> Epoch const [dateInput, setDateInput] = useState(""); - const [dateEpochSeconds, setDateEpochSeconds] = useState(null); - const [dateEpochMillis, setDateEpochMillis] = useState(null); - - // Live clock - const [liveEpoch, setLiveEpoch] = useState(Math.floor(Date.now() / 1000)); - - // Copy feedback + const [liveEpoch, setLiveEpoch] = useState(() => Math.floor(Date.now() / 1000)); const [copied, setCopied] = useState(null); + // Live clock effect useEffect(() => { const interval = setInterval(() => { setLiveEpoch(Math.floor(Date.now() / 1000)); @@ -80,48 +70,38 @@ export function EpochConverter() { setTimeout(() => setCopied(null), 1500); }, []); - // Parse epoch input - useEffect(() => { + // Derived from epochInput + const { parsedDate, epochError, isMillis } = React.useMemo(() => { if (!epochInput.trim()) { - setParsedDate(null); - setEpochError(""); - return; + return { parsedDate: null, epochError: "", isMillis: false }; } const num = Number(epochInput.trim()); if (isNaN(num)) { - setEpochError("Enter a valid number"); - setParsedDate(null); - return; + return { parsedDate: null, epochError: "Enter a valid number", isMillis: false }; } // Auto-detect: if > 10 digits, treat as milliseconds const isMs = epochInput.trim().length > 10; - setIsMillis(isMs); const ms = isMs ? num : num * 1000; const d = new Date(ms); if (isNaN(d.getTime())) { - setEpochError("Invalid timestamp"); - setParsedDate(null); - return; + return { parsedDate: null, epochError: "Invalid timestamp", isMillis: isMs }; } - setEpochError(""); - setParsedDate(d); + return { parsedDate: d, epochError: "", isMillis: isMs }; }, [epochInput]); - // Parse date input - useEffect(() => { + // Derived from dateInput + const { dateEpochSeconds, dateEpochMillis } = React.useMemo(() => { if (!dateInput) { - setDateEpochSeconds(null); - setDateEpochMillis(null); - return; + return { dateEpochSeconds: null, dateEpochMillis: null }; } const d = new Date(dateInput); if (isNaN(d.getTime())) { - setDateEpochSeconds(null); - setDateEpochMillis(null); - return; + return { dateEpochSeconds: null, dateEpochMillis: null }; } - setDateEpochSeconds(Math.floor(d.getTime() / 1000)); - setDateEpochMillis(d.getTime()); + return { + dateEpochSeconds: Math.floor(d.getTime() / 1000), + dateEpochMillis: d.getTime() + }; }, [dateInput]); const setNow = () => { diff --git a/components/convert/index.ts b/components/convert/index.ts new file mode 100644 index 0000000..1573b8a --- /dev/null +++ b/components/convert/index.ts @@ -0,0 +1 @@ +export * from "./ConvertPage"; diff --git a/components/docs/DocsPage.tsx b/components/docs/DocsPage.tsx new file mode 100644 index 0000000..8c62af0 --- /dev/null +++ b/components/docs/DocsPage.tsx @@ -0,0 +1,130 @@ +"use client"; + +import React from 'react'; +import { + Book, + Zap, + Shield, + Globe, + Braces, + Box, + Terminal, + Cpu +} from 'lucide-react'; + +import { Footer } from "@/components/landing/footer"; + +const DOCS_SECTIONS = [ + { + title: "Getting Started", + items: [ + { + icon: Zap, + label: "Introduction", + content: "Web Utils is a high-performance, universal file editor and code previewer designed for modern developers. It allows you to instantly edit, visualize, and format multiple data structures with zero setup." + }, + { + icon: Terminal, + label: "Quick Start", + content: "Navigate to the /view route, paste your code into the editor, and watch as it instantly renders in the preview pane. Use the sidebar to switch between output formats." + } + ] + }, + { + title: "Supported Formats", + items: [ + { + icon: Globe, + label: "HTML & Bootstrap", + content: "Full support for HTML5 and Bootstrap 5.3. Renders code in a safe, sandboxed iframe environment." + }, + { + icon: Braces, + label: "JSON & YAML", + content: "Automatic syntax validation and beautification for structured data. Error highlighting for invalid syntax." + }, + { + icon: Box, + label: "React (JSX/TSX)", + content: "Premium syntax highlighting for React components. Perfect for reviewing component structures." + } + ] + }, + { + title: "Advanced Features", + items: [ + { + icon: Cpu, + label: "Performance", + content: "Built on Next.js 15 for lightning-fast route transitions and optimized rendering cycles." + }, + { + icon: Shield, + label: "Security", + content: "All previews are sandboxed to prevent script execution from impacting the main application window." + } + ] + } +]; + +export function DocsPage() { + return ( +
+
+
+
+ < Book className="size-3" /> + Documentation +
+

+ Everything you need to
scale your workflow. +

+

+ A comprehensive guide to using and extending the Web Utils platform. +

+
+ +
+ {DOCS_SECTIONS.map((section) => ( +
+

+ + {section.title} +

+
+ {section.items.map((item) => ( +
+
+ +
+

{item.label}

+

+ {item.content} +

+
+ ))} +
+
+ ))} +
+ + {/* Footer info */} +
+

Ready to get started?

+

+ Join thousands of developers using Web Utils to edit and preview their code more efficiently. +

+
+ + +
+
+
+
+
+ ); +} diff --git a/components/docs/index.ts b/components/docs/index.ts new file mode 100644 index 0000000..bf3ef9b --- /dev/null +++ b/components/docs/index.ts @@ -0,0 +1 @@ +export * from "./DocsPage"; diff --git a/components/editor/EditorPage.tsx b/components/editor/EditorPage.tsx new file mode 100644 index 0000000..166a4df --- /dev/null +++ b/components/editor/EditorPage.tsx @@ -0,0 +1,17 @@ +"use client"; + +import { EditorContainer } from "./editor-container"; +import { DEFAULT_CONTENT } from "@/data/default-content"; + +export function EditorPage() { + return ( +
+
+ +
+
+ ); +} diff --git a/components/editor/index.ts b/components/editor/index.ts new file mode 100644 index 0000000..5d49c4a --- /dev/null +++ b/components/editor/index.ts @@ -0,0 +1 @@ +export * from "./EditorPage"; diff --git a/components/features/json/format.ts b/components/features/json/format.ts new file mode 100644 index 0000000..501465f --- /dev/null +++ b/components/features/json/format.ts @@ -0,0 +1,33 @@ +/** + * JSON formatting and validation logic + */ + +export function formatJson(jsonString: string, tabSize: number = 2): string { + try { + const obj = JSON.parse(jsonString); + return JSON.stringify(obj, null, tabSize); + } catch (error) { + console.error(`Error`,error); + throw new Error("Invalid JSON string"); + } +} + +export function validateJson(jsonString: string): { isValid: boolean; error?: string } { + try { + JSON.parse(jsonString); + return { isValid: true }; + } catch (error) { + console.error(`Error`,error); + return { isValid: false, error: error instanceof Error ? error.message : "Unknown error" }; + } +} + +export function minifyJson(jsonString: string): string { + try { + const obj = JSON.parse(jsonString); + return JSON.stringify(obj); + } catch (error : unknown) { + console.error(`Error`,error); + throw new Error("Invalid JSON string"); + } +} diff --git a/components/features/time/epoch.ts b/components/features/time/epoch.ts new file mode 100644 index 0000000..8b9de9c --- /dev/null +++ b/components/features/time/epoch.ts @@ -0,0 +1,33 @@ +/** + * Epoch and Date conversion logic + */ + +export function epochToDate(epoch: number): Date { + // Detect if seconds or milliseconds + const ms = epoch < 10000000000 ? epoch * 1000 : epoch; + return new Date(ms); +} + +export function dateToEpoch(date: Date, unit: 's' | 'ms' = 's'): number { + const ms = date.getTime(); + return unit === 's' ? Math.floor(ms / 1000) : ms; +} + +export function formatRelative(date: Date): string { + const now = new Date(); + const diff = now.getTime() - date.getTime(); + const seconds = Math.floor(diff / 1000); + const minutes = Math.floor(seconds / 60); + const hours = Math.floor(minutes / 60); + const days = Math.floor(hours / 24); + + if (days > 0) return `${days} day${days > 1 ? 's' : ''} ago`; + if (hours > 0) return `${hours} hour${hours > 1 ? 's' : ''} ago`; + if (minutes > 0) return `${minutes} minute${minutes > 1 ? 's' : ''} ago`; + return "just now"; +} + +export function isValidEpoch(val: unknown): boolean { + const num = Number(val); + return !isNaN(num) && num > 0; +} diff --git a/components/features/time/timezone.ts b/components/features/time/timezone.ts new file mode 100644 index 0000000..660ef8d --- /dev/null +++ b/components/features/time/timezone.ts @@ -0,0 +1,20 @@ +/** + * Timezone conversion logic + */ + +export function convertTimezone(date: Date, toTz: string): string { + return date.toLocaleString('en-US', { timeZone: toTz }); +} + +export const COMMON_TIMEZONES = [ + { label: "UTC", value: "UTC" }, + { label: "India (IST)", value: "Asia/Kolkata" }, + { label: "New York (EST/EDT)", value: "America/New_York" }, + { label: "London (GMT/BST)", value: "Europe/London" }, + { label: "Tokyo (JST)", value: "Asia/Tokyo" }, + { label: "Pacific (PST/PDT)", value: "America/Los_Angeles" } +]; + +export function getSystemTimezone(): string { + return Intl.DateTimeFormat().resolvedOptions().timeZone; +} diff --git a/components/ide/IDEPage.tsx b/components/ide/IDEPage.tsx new file mode 100644 index 0000000..88cd5c3 --- /dev/null +++ b/components/ide/IDEPage.tsx @@ -0,0 +1,59 @@ +"use client"; + +import React from 'react'; +import { Blocks, Rocket, Hammer, Construction } from 'lucide-react'; + +export function IDEPage() { + return ( +
+ {/* Background Decorative Elements */} +
+
+ +
+
+ + Under Development +
+ +

+ The Next-Gen
+ Cloud IDE +

+ +

+ Building a browser-based client WASM type build IDE with real-time collaboration, + WASM-powered runtimes, and deep integration. +

+ +
+
+ +

Fast Runtime

+

WASM Powered

+
+
+ +

Extensions

+

Rich Ecosystem

+
+
+ +

Cloud Build

+

Instant Deploy

+
+
+ +
+ + +
+
+ +
+ ); +} diff --git a/components/ide/index.ts b/components/ide/index.ts new file mode 100644 index 0000000..a406606 --- /dev/null +++ b/components/ide/index.ts @@ -0,0 +1 @@ +export * from "./IDEPage"; diff --git a/components/json/tree-viewer.tsx b/components/json/tree-viewer.tsx new file mode 100644 index 0000000..58963f1 --- /dev/null +++ b/components/json/tree-viewer.tsx @@ -0,0 +1,124 @@ + "use client"; + +import React, { useState } from 'react'; +import { + ChevronRight, + ChevronDown, + Copy, +} from 'lucide-react'; +import { cn } from "@/lib/utils"; +import { Badge } from "@/components/ui/badge"; +import { toast } from "sonner"; + +const JsonNode = ({ data, depth = 0, path = "", label }: { data: unknown, depth?: number, path: string, label?: string }) => { + const [isExpanded, setIsExpanded] = useState(depth < 2); + const type = typeof data; + const isObject = data !== null && type === 'object'; + const isArray = Array.isArray(data); + + const toggle = () => setIsExpanded(!isExpanded); + + const copyPath = (e: React.MouseEvent) => { + e.stopPropagation(); + navigator.clipboard.writeText(path); + toast.success(`Path copied: ${path}`); + }; + + if (isObject) { + const objData = data as Record; + const keys = Object.keys(objData); + const isEmpty = keys.length === 0; + + return ( +
+
+
+ {!isEmpty && ( + isExpanded ? : + )} + {isEmpty &&
} +
+ + {label && ( + {label}: + )} + + + {isArray ? `Array[${keys.length}]` : `Object{${keys.length}}`} + + + +
+ + {isExpanded && !isEmpty && ( +
+ {keys.map(key => ( + + ))} +
+ )} +
+ ); + } + + const numValue = Number(data); + + // Primitive values + return ( +
+
+ {label && ( + {label}: + )} + + {type === 'string' ? `"${data}"` : String(data)} + + + {/* Special "Smart Detection" for timestamps (Epoch) */} + {type === 'number' && numValue > 1000000000 && numValue < 3000000000 && ( + + Epoch Detected + + )} + + +
+ ); +}; + +export function JsonTreeViewer({ data }: { data: unknown }) { + if (!data) return
No JSON data to display
; + + return ( +
+ +
+ ); +} diff --git a/components/layout/client-layout.tsx b/components/layout/client-layout.tsx new file mode 100644 index 0000000..9b1df9a --- /dev/null +++ b/components/layout/client-layout.tsx @@ -0,0 +1,38 @@ +"use client"; + +import React, { useState } from "react"; +import { SidebarProvider } from "@/components/ui/sidebar"; +import { AppSidebar } from "@/components/layout/app-sidebar"; +import { SplashScreen } from "@/components/layout/splash-screen"; +import { CommandMenu } from "@/components/layout/command-menu"; +import { Navbar } from "@/components/layout/navbar"; + +export function ClientLayout({ children }: { children: React.ReactNode }) { + const [showSplash, setShowSplash] = useState(() => { + if (typeof window !== "undefined") { + return !localStorage.getItem("hasSeenSplash_v1"); + } + return true; + }); + + const handleSplashComplete = () => { + setShowSplash(false); + localStorage.setItem("hasSeenSplash_v1", "true"); + }; + + return ( + + {showSplash && } +
+ + +
+ +
+ {children} +
+
+
+
+ ); +} diff --git a/components/layout/command-menu.tsx b/components/layout/command-menu.tsx new file mode 100644 index 0000000..64440ea --- /dev/null +++ b/components/layout/command-menu.tsx @@ -0,0 +1,75 @@ +"use client"; + +import * as React from "react"; +import { + Settings, + User, +} from "lucide-react"; +import { useRouter } from "next/navigation"; + +import { + CommandDialog, + CommandEmpty, + CommandGroup, + CommandInput, + CommandItem, + CommandList, + CommandSeparator, + CommandShortcut, +} from "@/components/ui/command"; +import { TOOLS, type Tool } from "@/lib/constants/tools"; + +export function CommandMenu() { + const [open, setOpen] = React.useState(false); + const router = useRouter(); + + React.useEffect(() => { + const down = (e: KeyboardEvent) => { + if (e.key === "k" && (e.metaKey || e.ctrlKey)) { + e.preventDefault(); + setOpen((open) => !open); + } + }; + + document.addEventListener("keydown", down); + return () => document.removeEventListener("keydown", down); + }, []); + + const runCommand = React.useCallback((command: () => void) => { + setOpen(false); + command(); + }, []); + + return ( + + + + No results found. + + {TOOLS.map((tool: Tool) => ( + runCommand(() => router.push(tool.href))} + > + + {tool.name} + + ))} + + + + runCommand(() => {})}> + + Profile + ⌘P + + runCommand(() => {})}> + + Settings + ⌘S + + + + + ); +} diff --git a/components/layout/ide-workspace.tsx b/components/layout/ide-workspace.tsx new file mode 100644 index 0000000..f2dc342 --- /dev/null +++ b/components/layout/ide-workspace.tsx @@ -0,0 +1,269 @@ +"use client"; + +import React, { useState } from 'react'; +import Editor from '@monaco-editor/react'; +import { + Panel, + Group as PanelGroup, + Separator as PanelResizeHandle +} from 'react-resizable-panels'; +import { + Play, + Save, + RotateCcw, + Eye, + Braces, + Clock, + Terminal as TerminalIcon, + Globe +} from 'lucide-react'; +import { Button } from "@/components/ui/button"; +import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; +import { JsonTreeViewer } from "@/components/json/tree-viewer"; +import { useTheme } from "next-themes"; + +export function IdeWorkspace() { + const [code, setCode] = useState('{\n "name": "Web Utils Pro",\n "status": "Ready",\n "timestamp": 1711206600\n}'); + const [language, setLanguage] = useState('json'); + const { theme } = useTheme(); + + const [terminalInput, setTerminalInput] = useState(''); + const [terminalOutput, setTerminalOutput] = useState([ + 'system: ide workspace activated', + 'system: terminal ready' + ]); + + const handleTerminalSubmit = () => { + if (!terminalInput.trim()) return; + + const cmd = terminalInput.toLowerCase(); + const newOutput = [...terminalOutput, `$${terminalInput}`]; + + switch(cmd) { + case 'help': + newOutput.push('available commands:'); + newOutput.push(' - clear: clear the terminal'); + newOutput.push(' - json-format: format the editor content'); + newOutput.push(' - time: show current epoch'); + newOutput.push(' - build: simulate build process'); + break; + case 'clear': + setTerminalOutput([]); + setTerminalInput(''); + return; + case 'json-format': + try { + const formatted = JSON.stringify(JSON.parse(code), null, 2); + setCode(formatted); + newOutput.push('success: json formatted'); + } catch(e: unknown) { + console.error('Error : ', e); + newOutput.push(`error: invalid json `); + } + break; + case 'time': + newOutput.push(`current epoch: ${Math.floor(Date.now() / 1000)}`); + break; + case 'build': + newOutput.push('build: starting optimization...'); + newOutput.push('build: generated 18 static pages'); + newOutput.push('build: success in 234ms'); + break; + default: + newOutput.push(`error: command not found: ${cmd}`); + } + + setTerminalOutput(newOutput); + setTerminalInput(''); + }; + + const handleEditorChange = (value: string | undefined) => { + if (value) setCode(value); + }; + + let parsedJson = null; + try { + parsedJson = JSON.parse(code); + } catch (e: unknown) { + console.error('Error : ', e); + } + + return ( +
+ {/* IDE Toolbar */} +
+
+
+ +
+
+ +
+ + + +
+
+ + {/* Main Workspace */} + + {/* Editor Panel */} + +
+ +
+
+ + + + {/* Right Panel (Tools & Preview) */} + +
+ +
+ + + Preview + + + JSON Tree + + + Time + + + Terminal + + + Console + + +
+ +
+ + {language === 'html' ? ( +
+