feat: add light theme WIP
This commit is contained in:
parent
68964c2c8f
commit
eb4e34cf10
@ -1,4 +1,4 @@
|
|||||||
frontend/**
|
# frontend/**
|
||||||
backend/ai/**
|
backend/ai/**
|
||||||
backend/database/**
|
backend/database/**
|
||||||
backend/storage/**
|
backend/storage/**
|
@ -99,6 +99,29 @@
|
|||||||
); /* violet 900 -> bg */
|
); /* violet 900 -> bg */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.light .gradient-button-bg {
|
||||||
|
background: radial-gradient(
|
||||||
|
circle at top,
|
||||||
|
#262626 0%,
|
||||||
|
#f5f5f5 50%
|
||||||
|
); /* Dark gray -> Light gray */
|
||||||
|
}
|
||||||
|
|
||||||
|
.light .gradient-button {
|
||||||
|
background: radial-gradient(
|
||||||
|
circle at bottom,
|
||||||
|
hsl(0, 10%, 25%) -10%,
|
||||||
|
#9d9d9d 50%
|
||||||
|
); /* Light gray -> Almost white */
|
||||||
|
}
|
||||||
|
|
||||||
|
.light .gradient-button-bg > div:hover {
|
||||||
|
background: radial-gradient(
|
||||||
|
circle at bottom,
|
||||||
|
hsl(0, 10%, 25%) -10%,
|
||||||
|
#9d9d9d 80%
|
||||||
|
); /* Light gray -> Almost white */
|
||||||
|
}
|
||||||
.inline-decoration::before {
|
.inline-decoration::before {
|
||||||
content: "Generate";
|
content: "Generate";
|
||||||
color: #525252;
|
color: #525252;
|
||||||
|
@ -1,13 +1,13 @@
|
|||||||
import type { Metadata } from "next"
|
|
||||||
import { GeistSans } from "geist/font/sans"
|
|
||||||
import { GeistMono } from "geist/font/mono"
|
|
||||||
import "./globals.css"
|
|
||||||
import { ThemeProvider } from "@/components/layout/themeProvider"
|
|
||||||
import { ClerkProvider } from "@clerk/nextjs"
|
|
||||||
import { Toaster } from "@/components/ui/sonner"
|
import { Toaster } from "@/components/ui/sonner"
|
||||||
import { Analytics } from "@vercel/analytics/react"
|
import { ThemeProvider } from "@/components/ui/theme-provider"
|
||||||
import { PreviewProvider } from "@/context/PreviewContext";
|
import { PreviewProvider } from "@/context/PreviewContext"
|
||||||
import { SocketProvider } from '@/context/SocketContext'
|
import { SocketProvider } from '@/context/SocketContext'
|
||||||
|
import { ClerkProvider } from "@clerk/nextjs"
|
||||||
|
import { Analytics } from "@vercel/analytics/react"
|
||||||
|
import { GeistMono } from "geist/font/mono"
|
||||||
|
import { GeistSans } from "geist/font/sans"
|
||||||
|
import type { Metadata } from "next"
|
||||||
|
import "./globals.css"
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: "Sandbox",
|
title: "Sandbox",
|
||||||
@ -25,8 +25,7 @@ export default function RootLayout({
|
|||||||
<body>
|
<body>
|
||||||
<ThemeProvider
|
<ThemeProvider
|
||||||
attribute="class"
|
attribute="class"
|
||||||
defaultTheme="dark"
|
defaultTheme="system"
|
||||||
forcedTheme="dark"
|
|
||||||
disableTransitionOnChange
|
disableTransitionOnChange
|
||||||
>
|
>
|
||||||
<SocketProvider>
|
<SocketProvider>
|
||||||
|
@ -3,16 +3,14 @@
|
|||||||
import {
|
import {
|
||||||
Dialog,
|
Dialog,
|
||||||
DialogContent,
|
DialogContent,
|
||||||
DialogDescription,
|
|
||||||
DialogHeader,
|
DialogHeader,
|
||||||
DialogTitle,
|
DialogTitle,
|
||||||
DialogTrigger,
|
|
||||||
} from "@/components/ui/dialog"
|
} from "@/components/ui/dialog"
|
||||||
import Image from "next/image"
|
|
||||||
import { useState, useCallback, useEffect, useMemo } from "react"
|
|
||||||
import { set, z } from "zod"
|
|
||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
|
import Image from "next/image"
|
||||||
|
import { useCallback, useEffect, useMemo, useState } from "react"
|
||||||
import { useForm } from "react-hook-form"
|
import { useForm } from "react-hook-form"
|
||||||
|
import { z } from "zod"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
@ -31,23 +29,17 @@ import {
|
|||||||
SelectTrigger,
|
SelectTrigger,
|
||||||
SelectValue,
|
SelectValue,
|
||||||
} from "@/components/ui/select"
|
} from "@/components/ui/select"
|
||||||
import { useUser } from "@clerk/nextjs"
|
|
||||||
import { createSandbox } from "@/lib/actions"
|
import { createSandbox } from "@/lib/actions"
|
||||||
import { useRouter } from "next/navigation"
|
|
||||||
import {
|
|
||||||
Loader2,
|
|
||||||
ChevronRight,
|
|
||||||
ChevronLeft,
|
|
||||||
Search,
|
|
||||||
SlashSquare,
|
|
||||||
} from "lucide-react"
|
|
||||||
import { Button } from "../ui/button"
|
|
||||||
import { projectTemplates } from "@/lib/data"
|
import { projectTemplates } from "@/lib/data"
|
||||||
|
import { useUser } from "@clerk/nextjs"
|
||||||
|
import { ChevronLeft, ChevronRight, Loader2, Search } from "lucide-react"
|
||||||
|
import { useRouter } from "next/navigation"
|
||||||
|
import { Button } from "../ui/button"
|
||||||
|
|
||||||
import useEmblaCarousel from "embla-carousel-react"
|
|
||||||
import type { EmblaCarouselType } from "embla-carousel"
|
|
||||||
import { WheelGesturesPlugin } from "embla-carousel-wheel-gestures"
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
import type { EmblaCarouselType } from "embla-carousel"
|
||||||
|
import useEmblaCarousel from "embla-carousel-react"
|
||||||
|
import { WheelGesturesPlugin } from "embla-carousel-wheel-gestures"
|
||||||
const formSchema = z.object({
|
const formSchema = z.object({
|
||||||
name: z
|
name: z
|
||||||
.string()
|
.string()
|
||||||
@ -296,7 +288,7 @@ function SearchInput({
|
|||||||
<form {...{ onSubmit }} className="w-40 h-8 ">
|
<form {...{ onSubmit }} className="w-40 h-8 ">
|
||||||
<label
|
<label
|
||||||
htmlFor="template-search"
|
htmlFor="template-search"
|
||||||
className="flex gap-2 rounded-sm transition-colors bg-[#2e2e2e] border border-[--s-color] [--s-color:hsl(var(--muted-foreground))] focus-within:[--s-color:#fff] h-full items-center px-2"
|
className="flex gap-2 rounded-sm transition-colors bg-gray-100 dark:bg-[#2e2e2e] border border-[--s-color] [--s-color:hsl(var(--muted-foreground))] focus-within:[--s-color:#fff] h-full items-center px-2"
|
||||||
>
|
>
|
||||||
<Search className="size-4 text-[--s-color] transition-colors" />
|
<Search className="size-4 text-[--s-color] transition-colors" />
|
||||||
<input
|
<input
|
||||||
|
@ -1,43 +1,57 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { SetStateAction, useCallback, useEffect, useRef, useState } from "react"
|
|
||||||
import * as monaco from "monaco-editor"
|
|
||||||
import Editor, { BeforeMount, OnMount } from "@monaco-editor/react"
|
|
||||||
import { toast } from "sonner"
|
|
||||||
import { useClerk } from "@clerk/nextjs"
|
import { useClerk } from "@clerk/nextjs"
|
||||||
|
import Editor, { BeforeMount, OnMount } from "@monaco-editor/react"
|
||||||
import { AnimatePresence, motion } from "framer-motion"
|
import { AnimatePresence, motion } from "framer-motion"
|
||||||
|
import * as monaco from "monaco-editor"
|
||||||
|
import { useCallback, useEffect, useRef, useState } from "react"
|
||||||
|
import { toast } from "sonner"
|
||||||
|
|
||||||
import * as Y from "yjs"
|
import { TypedLiveblocksProvider, useRoom, useSelf } from "@/liveblocks.config"
|
||||||
import LiveblocksProvider from "@liveblocks/yjs"
|
import LiveblocksProvider from "@liveblocks/yjs"
|
||||||
import { MonacoBinding } from "y-monaco"
|
import { MonacoBinding } from "y-monaco"
|
||||||
import { Awareness } from "y-protocols/awareness"
|
import { Awareness } from "y-protocols/awareness"
|
||||||
import { TypedLiveblocksProvider, useRoom, useSelf } from "@/liveblocks.config"
|
import * as Y from "yjs"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
ResizableHandle,
|
ResizableHandle,
|
||||||
ResizablePanel,
|
ResizablePanel,
|
||||||
ResizablePanelGroup,
|
ResizablePanelGroup,
|
||||||
} from "@/components/ui/resizable"
|
} from "@/components/ui/resizable"
|
||||||
import { FileJson, Loader2, Sparkles, TerminalSquare, ArrowDownToLine, ArrowRightToLine } from "lucide-react"
|
import { PreviewProvider, usePreview } from "@/context/PreviewContext"
|
||||||
import Tab from "../ui/tab"
|
import { useSocket } from "@/context/SocketContext"
|
||||||
import Sidebar from "./sidebar"
|
import { parseTSConfigToMonacoOptions } from "@/lib/tsconfig"
|
||||||
import GenerateInput from "./generate"
|
import { Sandbox, TFile, TFolder, TTab, User } from "@/lib/types"
|
||||||
import { Sandbox, User, TFile, TFolder, TTab } from "@/lib/types"
|
import {
|
||||||
import { addNew, processFileType, validateName, debounce } from "@/lib/utils"
|
addNew,
|
||||||
import { Cursors } from "./live/cursors"
|
cn,
|
||||||
|
debounce,
|
||||||
|
deepMerge,
|
||||||
|
processFileType,
|
||||||
|
validateName,
|
||||||
|
} from "@/lib/utils"
|
||||||
import { Terminal } from "@xterm/xterm"
|
import { Terminal } from "@xterm/xterm"
|
||||||
|
import {
|
||||||
|
ArrowDownToLine,
|
||||||
|
ArrowRightToLine,
|
||||||
|
FileJson,
|
||||||
|
Loader2,
|
||||||
|
Sparkles,
|
||||||
|
TerminalSquare,
|
||||||
|
} from "lucide-react"
|
||||||
|
import { useTheme } from "next-themes"
|
||||||
|
import React from "react"
|
||||||
|
import { ImperativePanelHandle } from "react-resizable-panels"
|
||||||
|
import { Button } from "../ui/button"
|
||||||
|
import Tab from "../ui/tab"
|
||||||
|
import AIChat from "./AIChat"
|
||||||
|
import GenerateInput from "./generate"
|
||||||
|
import { Cursors } from "./live/cursors"
|
||||||
import DisableAccessModal from "./live/disableModal"
|
import DisableAccessModal from "./live/disableModal"
|
||||||
import Loading from "./loading"
|
import Loading from "./loading"
|
||||||
import PreviewWindow from "./preview"
|
import PreviewWindow from "./preview"
|
||||||
|
import Sidebar from "./sidebar"
|
||||||
import Terminals from "./terminals"
|
import Terminals from "./terminals"
|
||||||
import { ImperativePanelHandle } from "react-resizable-panels"
|
|
||||||
import { PreviewProvider, usePreview } from "@/context/PreviewContext"
|
|
||||||
import { useSocket } from "@/context/SocketContext"
|
|
||||||
import { Button } from "../ui/button"
|
|
||||||
import React from "react"
|
|
||||||
import { parseTSConfigToMonacoOptions } from "@/lib/tsconfig"
|
|
||||||
import { cn, deepMerge } from "@/lib/utils"
|
|
||||||
import AIChat from "./AIChat"
|
|
||||||
|
|
||||||
export default function CodeEditor({
|
export default function CodeEditor({
|
||||||
userData,
|
userData,
|
||||||
@ -48,7 +62,8 @@ export default function CodeEditor({
|
|||||||
}) {
|
}) {
|
||||||
//SocketContext functions and effects
|
//SocketContext functions and effects
|
||||||
const { socket, setUserAndSandboxId } = useSocket()
|
const { socket, setUserAndSandboxId } = useSocket()
|
||||||
|
// theme
|
||||||
|
const { theme } = useTheme()
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
// Ensure userData.id and sandboxData.id are available before attempting to connect
|
// Ensure userData.id and sandboxData.id are available before attempting to connect
|
||||||
if (userData.id && sandboxData.id) {
|
if (userData.id && sandboxData.id) {
|
||||||
@ -75,10 +90,10 @@ export default function CodeEditor({
|
|||||||
})
|
})
|
||||||
|
|
||||||
// Layout state
|
// Layout state
|
||||||
const [isHorizontalLayout, setIsHorizontalLayout] = useState(false);
|
const [isHorizontalLayout, setIsHorizontalLayout] = useState(false)
|
||||||
|
|
||||||
// AI Chat state
|
// AI Chat state
|
||||||
const [isAIChatOpen, setIsAIChatOpen] = useState(false);
|
const [isAIChatOpen, setIsAIChatOpen] = useState(false)
|
||||||
|
|
||||||
// File state
|
// File state
|
||||||
const [files, setFiles] = useState<(TFolder | TFile)[]>([])
|
const [files, setFiles] = useState<(TFolder | TFile)[]>([])
|
||||||
@ -91,6 +106,7 @@ export default function CodeEditor({
|
|||||||
|
|
||||||
// Editor state
|
// Editor state
|
||||||
const [editorLanguage, setEditorLanguage] = useState("plaintext")
|
const [editorLanguage, setEditorLanguage] = useState("plaintext")
|
||||||
|
console.log("editor language: ",editorLanguage)
|
||||||
const [cursorLine, setCursorLine] = useState(0)
|
const [cursorLine, setCursorLine] = useState(0)
|
||||||
const [editorRef, setEditorRef] =
|
const [editorRef, setEditorRef] =
|
||||||
useState<monaco.editor.IStandaloneCodeEditor>()
|
useState<monaco.editor.IStandaloneCodeEditor>()
|
||||||
@ -152,7 +168,7 @@ export default function CodeEditor({
|
|||||||
const generateRef = useRef<HTMLDivElement>(null)
|
const generateRef = useRef<HTMLDivElement>(null)
|
||||||
const suggestionRef = useRef<HTMLDivElement>(null)
|
const suggestionRef = useRef<HTMLDivElement>(null)
|
||||||
const generateWidgetRef = useRef<HTMLDivElement>(null)
|
const generateWidgetRef = useRef<HTMLDivElement>(null)
|
||||||
const { previewPanelRef } = usePreview();
|
const { previewPanelRef } = usePreview()
|
||||||
const editorPanelRef = useRef<ImperativePanelHandle>(null)
|
const editorPanelRef = useRef<ImperativePanelHandle>(null)
|
||||||
const previewWindowRef = useRef<{ refreshIframe: () => void }>(null)
|
const previewWindowRef = useRef<{ refreshIframe: () => void }>(null)
|
||||||
|
|
||||||
@ -526,10 +542,10 @@ export default function CodeEditor({
|
|||||||
const down = (e: KeyboardEvent) => {
|
const down = (e: KeyboardEvent) => {
|
||||||
if (e.key === "s" && (e.metaKey || e.ctrlKey)) {
|
if (e.key === "s" && (e.metaKey || e.ctrlKey)) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
debouncedSaveData(activeFileId);
|
debouncedSaveData(activeFileId)
|
||||||
} else if (e.key === "l" && (e.metaKey || e.ctrlKey)) {
|
} else if (e.key === "l" && (e.metaKey || e.ctrlKey)) {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
setIsAIChatOpen(prev => !prev);
|
setIsAIChatOpen((prev) => !prev)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
document.addEventListener("keydown", down)
|
document.addEventListener("keydown", down)
|
||||||
@ -843,17 +859,17 @@ export default function CodeEditor({
|
|||||||
|
|
||||||
const togglePreviewPanel = () => {
|
const togglePreviewPanel = () => {
|
||||||
if (isPreviewCollapsed) {
|
if (isPreviewCollapsed) {
|
||||||
previewPanelRef.current?.expand();
|
previewPanelRef.current?.expand()
|
||||||
setIsPreviewCollapsed(false);
|
setIsPreviewCollapsed(false)
|
||||||
} else {
|
} else {
|
||||||
previewPanelRef.current?.collapse();
|
previewPanelRef.current?.collapse()
|
||||||
setIsPreviewCollapsed(true);
|
setIsPreviewCollapsed(true)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
const toggleLayout = () => {
|
const toggleLayout = () => {
|
||||||
setIsHorizontalLayout(prev => !prev);
|
setIsHorizontalLayout((prev) => !prev)
|
||||||
};
|
}
|
||||||
|
|
||||||
// On disabled access for shared users, show un-interactable loading placeholder + info modal
|
// On disabled access for shared users, show un-interactable loading placeholder + info modal
|
||||||
if (disableAccess.isDisabled)
|
if (disableAccess.isDisabled)
|
||||||
@ -994,7 +1010,9 @@ export default function CodeEditor({
|
|||||||
<ResizablePanelGroup direction="horizontal">
|
<ResizablePanelGroup direction="horizontal">
|
||||||
{/* Left side: Editor and Preview/Terminal */}
|
{/* Left side: Editor and Preview/Terminal */}
|
||||||
<ResizablePanel defaultSize={isAIChatOpen ? 80 : 100} minSize={50}>
|
<ResizablePanel defaultSize={isAIChatOpen ? 80 : 100} minSize={50}>
|
||||||
<ResizablePanelGroup direction={isHorizontalLayout ? "vertical" : "horizontal"}>
|
<ResizablePanelGroup
|
||||||
|
direction={isHorizontalLayout ? "vertical" : "horizontal"}
|
||||||
|
>
|
||||||
<ResizablePanel
|
<ResizablePanel
|
||||||
className="p-2 flex flex-col"
|
className="p-2 flex flex-col"
|
||||||
maxSize={80}
|
maxSize={80}
|
||||||
@ -1044,7 +1062,7 @@ export default function CodeEditor({
|
|||||||
onChange={(value) => {
|
onChange={(value) => {
|
||||||
// If the new content is different from the cached content, update it
|
// If the new content is different from the cached content, update it
|
||||||
if (value !== fileContents[activeFileId]) {
|
if (value !== fileContents[activeFileId]) {
|
||||||
setActiveFileContent(value ?? ""); // Update the active file content
|
setActiveFileContent(value ?? "") // Update the active file content
|
||||||
// Mark the file as unsaved by setting 'saved' to false
|
// Mark the file as unsaved by setting 'saved' to false
|
||||||
setTabs((prev) =>
|
setTabs((prev) =>
|
||||||
prev.map((tab) =>
|
prev.map((tab) =>
|
||||||
@ -1077,7 +1095,7 @@ export default function CodeEditor({
|
|||||||
fixedOverflowWidgets: true,
|
fixedOverflowWidgets: true,
|
||||||
fontFamily: "var(--font-geist-mono)",
|
fontFamily: "var(--font-geist-mono)",
|
||||||
}}
|
}}
|
||||||
theme="vs-dark"
|
theme={theme === "light" ? "vs" : "vs-dark"}
|
||||||
value={activeFileContent}
|
value={activeFileContent}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
@ -1091,25 +1109,39 @@ export default function CodeEditor({
|
|||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
<ResizableHandle />
|
<ResizableHandle />
|
||||||
<ResizablePanel defaultSize={30}>
|
<ResizablePanel defaultSize={30}>
|
||||||
<ResizablePanelGroup direction={
|
<ResizablePanelGroup
|
||||||
isAIChatOpen && isHorizontalLayout ? "horizontal" :
|
direction={
|
||||||
isAIChatOpen ? "vertical" :
|
isAIChatOpen && isHorizontalLayout
|
||||||
isHorizontalLayout ? "horizontal" :
|
? "horizontal"
|
||||||
"vertical"
|
: isAIChatOpen
|
||||||
}>
|
? "vertical"
|
||||||
|
: isHorizontalLayout
|
||||||
|
? "horizontal"
|
||||||
|
: "vertical"
|
||||||
|
}
|
||||||
|
>
|
||||||
<ResizablePanel
|
<ResizablePanel
|
||||||
ref={previewPanelRef}
|
ref={previewPanelRef}
|
||||||
defaultSize={isPreviewCollapsed ? 4 : 20}
|
defaultSize={isPreviewCollapsed ? 4 : 20}
|
||||||
minSize={25}
|
minSize={25}
|
||||||
collapsedSize={isHorizontalLayout ? 20 : 4}
|
collapsedSize={isHorizontalLayout ? 20 : 4}
|
||||||
className="p-2 flex flex-col"
|
className="p-2 flex flex-col gap-2"
|
||||||
collapsible
|
collapsible
|
||||||
onCollapse={() => setIsPreviewCollapsed(true)}
|
onCollapse={() => setIsPreviewCollapsed(true)}
|
||||||
onExpand={() => setIsPreviewCollapsed(false)}
|
onExpand={() => setIsPreviewCollapsed(false)}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between">
|
<div className="flex items-center justify-between">
|
||||||
<Button onClick={toggleLayout} size="sm" variant="ghost" className="mr-2 border">
|
<Button
|
||||||
{isHorizontalLayout ? <ArrowRightToLine className="w-4 h-4" /> : <ArrowDownToLine className="w-4 h-4" />}
|
onClick={toggleLayout}
|
||||||
|
size="sm"
|
||||||
|
variant="ghost"
|
||||||
|
className="mr-2 border"
|
||||||
|
>
|
||||||
|
{isHorizontalLayout ? (
|
||||||
|
<ArrowRightToLine className="w-4 h-4" />
|
||||||
|
) : (
|
||||||
|
<ArrowDownToLine className="w-4 h-4" />
|
||||||
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
<PreviewWindow
|
<PreviewWindow
|
||||||
open={togglePreviewPanel}
|
open={togglePreviewPanel}
|
||||||
@ -1154,7 +1186,10 @@ export default function CodeEditor({
|
|||||||
<ResizablePanel defaultSize={30} minSize={15}>
|
<ResizablePanel defaultSize={30} minSize={15}>
|
||||||
<AIChat
|
<AIChat
|
||||||
activeFileContent={activeFileContent}
|
activeFileContent={activeFileContent}
|
||||||
activeFileName={tabs.find(tab => tab.id === activeFileId)?.name || 'No file selected'}
|
activeFileName={
|
||||||
|
tabs.find((tab) => tab.id === activeFileId)?.name ||
|
||||||
|
"No file selected"
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
</ResizablePanel>
|
</ResizablePanel>
|
||||||
</>
|
</>
|
||||||
|
@ -1,33 +1,34 @@
|
|||||||
"use client";
|
"use client"
|
||||||
|
|
||||||
import Image from "next/image";
|
import Logo from "@/assets/logo.svg"
|
||||||
import Logo from "@/assets/logo.svg";
|
import { Button } from "@/components/ui/button"
|
||||||
import { Pencil, Users } from "lucide-react";
|
import { ThemeSwitcher } from "@/components/ui/theme-switcher"
|
||||||
import Link from "next/link";
|
import UserButton from "@/components/ui/userButton"
|
||||||
import { Sandbox, User } from "@/lib/types";
|
import { Sandbox, User } from "@/lib/types"
|
||||||
import UserButton from "@/components/ui/userButton";
|
import { Pencil, Users } from "lucide-react"
|
||||||
import { Button } from "@/components/ui/button";
|
import Image from "next/image"
|
||||||
import { useState } from "react";
|
import Link from "next/link"
|
||||||
import EditSandboxModal from "./edit";
|
import { useState } from "react"
|
||||||
import ShareSandboxModal from "./share";
|
import { Avatars } from "../live/avatars"
|
||||||
import { Avatars } from "../live/avatars";
|
import DeployButtonModal from "./deploy"
|
||||||
import RunButtonModal from "./run";
|
import EditSandboxModal from "./edit"
|
||||||
import DeployButtonModal from "./deploy";
|
import RunButtonModal from "./run"
|
||||||
|
import ShareSandboxModal from "./share"
|
||||||
|
|
||||||
export default function Navbar({
|
export default function Navbar({
|
||||||
userData,
|
userData,
|
||||||
sandboxData,
|
sandboxData,
|
||||||
shared,
|
shared,
|
||||||
}: {
|
}: {
|
||||||
userData: User;
|
userData: User
|
||||||
sandboxData: Sandbox;
|
sandboxData: Sandbox
|
||||||
shared: { id: string; name: string }[];
|
shared: { id: string; name: string }[]
|
||||||
}) {
|
}) {
|
||||||
const [isEditOpen, setIsEditOpen] = useState(false);
|
const [isEditOpen, setIsEditOpen] = useState(false)
|
||||||
const [isShareOpen, setIsShareOpen] = useState(false);
|
const [isShareOpen, setIsShareOpen] = useState(false)
|
||||||
const [isRunning, setIsRunning] = useState(false);
|
const [isRunning, setIsRunning] = useState(false)
|
||||||
|
|
||||||
const isOwner = sandboxData.userId === userData.id;;
|
const isOwner = sandboxData.userId === userData.id
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
@ -72,19 +73,17 @@ export default function Navbar({
|
|||||||
|
|
||||||
{isOwner ? (
|
{isOwner ? (
|
||||||
<>
|
<>
|
||||||
<DeployButtonModal
|
<DeployButtonModal data={sandboxData} userData={userData} />
|
||||||
data={sandboxData}
|
|
||||||
userData={userData}
|
|
||||||
/>
|
|
||||||
<Button variant="outline" onClick={() => setIsShareOpen(true)}>
|
<Button variant="outline" onClick={() => setIsShareOpen(true)}>
|
||||||
<Users className="w-4 h-4 mr-2" />
|
<Users className="w-4 h-4 mr-2" />
|
||||||
Share
|
Share
|
||||||
</Button>
|
</Button>
|
||||||
</>
|
</>
|
||||||
) : null}
|
) : null}
|
||||||
|
<ThemeSwitcher />
|
||||||
<UserButton userData={userData} />
|
<UserButton userData={userData} />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
);
|
)
|
||||||
}
|
}
|
@ -32,7 +32,27 @@ export default function EditorTerminal({
|
|||||||
const terminal = new Terminal({
|
const terminal = new Terminal({
|
||||||
cursorBlink: true,
|
cursorBlink: true,
|
||||||
theme: {
|
theme: {
|
||||||
background: "#262626",
|
foreground: "#2e3436",
|
||||||
|
background: "#ffffff",
|
||||||
|
black: "#2e3436",
|
||||||
|
brightBlack: "#555753",
|
||||||
|
red: "#cc0000",
|
||||||
|
brightRed: "#ef2929",
|
||||||
|
green: "#4e9a06",
|
||||||
|
brightGreen: "#8ae234",
|
||||||
|
yellow: "#c4a000",
|
||||||
|
brightYellow: "#fce94f",
|
||||||
|
blue: "#3465a4",
|
||||||
|
brightBlue: "#729fcf",
|
||||||
|
magenta: "#75507b",
|
||||||
|
brightMagenta: "#ad7fa8",
|
||||||
|
cyan: "#06989a",
|
||||||
|
brightCyan: "#34e2e2",
|
||||||
|
white: "#d3d7cf",
|
||||||
|
brightWhite: "#eeeeec",
|
||||||
|
cursor: "#2e3436",
|
||||||
|
cursorAccent: "#ffffff",
|
||||||
|
selection: "rgba(52, 101, 164, 0.3)",
|
||||||
},
|
},
|
||||||
fontFamily: "var(--font-geist-mono)",
|
fontFamily: "var(--font-geist-mono)",
|
||||||
fontSize: 14,
|
fontSize: 14,
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
import Image from "next/image"
|
|
||||||
import Logo from "@/assets/logo.svg"
|
import Logo from "@/assets/logo.svg"
|
||||||
import XLogo from "@/assets/x.svg"
|
import CustomButton from "@/components/ui/customButton"
|
||||||
import Button from "@/components/ui/customButton"
|
|
||||||
import { ChevronRight } from "lucide-react"
|
import { ChevronRight } from "lucide-react"
|
||||||
|
import Image from "next/image"
|
||||||
import Link from "next/link"
|
import Link from "next/link"
|
||||||
|
import { Button } from "../ui/button"
|
||||||
|
import { ThemeSwitcher } from "../ui/theme-switcher"
|
||||||
|
|
||||||
export default function Landing() {
|
export default function Landing() {
|
||||||
return (
|
return (
|
||||||
@ -20,21 +21,37 @@ export default function Landing() {
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex items-center space-x-4">
|
<div className="flex items-center space-x-4">
|
||||||
|
<Button variant="outline" size="icon" asChild>
|
||||||
<a href="https://www.x.com/ishaandey_" target="_blank">
|
<a href="https://www.x.com/ishaandey_" target="_blank">
|
||||||
<Image src={XLogo} alt="X Logo" width={18} height={18} />
|
<svg
|
||||||
|
width="1200"
|
||||||
|
height="1227"
|
||||||
|
viewBox="0 0 1200 1227"
|
||||||
|
fill="none"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
className="size-[1.125rem] text-muted-foreground"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
d="M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z"
|
||||||
|
fill="currentColor"
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
</a>
|
</a>
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<ThemeSwitcher />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h1 className="text-2xl font-medium text-center mt-16">
|
<h1 className="text-2xl font-medium text-center mt-16">
|
||||||
A Collaborative + AI-Powered Code Environment
|
A Collaborative + AI-Powered Code Environment
|
||||||
</h1>
|
</h1>
|
||||||
<div className="text-muted-foreground mt-4 text-center ">
|
<p className="text-muted-foreground mt-4 text-center ">
|
||||||
Sandbox is an open-source cloud-based code editing environment with
|
Sandbox is an open-source cloud-based code editing environment with
|
||||||
custom AI code autocompletion and real-time collaboration.
|
custom AI code autocompletion and real-time collaboration.
|
||||||
</div>
|
</p>
|
||||||
<div className="mt-8 flex space-x-4">
|
<div className="mt-8 flex space-x-4">
|
||||||
<Link href="/sign-up">
|
<Link href="/sign-up">
|
||||||
<Button>Go To App</Button>
|
<CustomButton>Go To App</CustomButton>
|
||||||
</Link>
|
</Link>
|
||||||
<a
|
<a
|
||||||
href="https://github.com/ishaan1013/sandbox"
|
href="https://github.com/ishaan1013/sandbox"
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import * as React from "react"
|
|
||||||
import { Plus } from "lucide-react"
|
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
|
import * as React from "react"
|
||||||
|
|
||||||
const Button = ({
|
const Button = ({
|
||||||
children,
|
children,
|
||||||
@ -25,7 +24,7 @@ const Button = ({
|
|||||||
`gradient-button-bg p-[1px] inline-flex group rounded-md text-sm font-medium focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50`
|
`gradient-button-bg p-[1px] inline-flex group rounded-md text-sm font-medium focus-visible:ring-offset-2 focus-visible:ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50`
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
<div className="rounded-[6px] w-full gradient-button flex items-center justify-center whitespace-nowrap px-4 py-2 h-9">
|
<div className="rounded-[6px] w-full gradient-button transition-colors flex items-center justify-center whitespace-nowrap px-4 py-2 h-9">
|
||||||
{children}
|
{children}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</button>
|
||||||
|
@ -1,9 +1,9 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import * as React from "react"
|
|
||||||
import { ThemeProvider as NextThemesProvider } from "next-themes"
|
import { ThemeProvider as NextThemesProvider } from "next-themes"
|
||||||
import { type ThemeProviderProps } from "next-themes/dist/types"
|
import { type ThemeProviderProps } from "next-themes/dist/types"
|
||||||
|
|
||||||
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
export function ThemeProvider({ children, ...props }: ThemeProviderProps) {
|
||||||
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
|
return <NextThemesProvider {...props}>{children}</NextThemesProvider>
|
||||||
}
|
}
|
||||||
|
|
39
frontend/components/ui/theme-switcher.tsx
Normal file
39
frontend/components/ui/theme-switcher.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
import { Moon, Sun } from "lucide-react"
|
||||||
|
import { useTheme } from "next-themes"
|
||||||
|
|
||||||
|
import { Button } from "@/components/ui/button"
|
||||||
|
import {
|
||||||
|
DropdownMenu,
|
||||||
|
DropdownMenuContent,
|
||||||
|
DropdownMenuItem,
|
||||||
|
DropdownMenuTrigger,
|
||||||
|
} from "@/components/ui/dropdown-menu"
|
||||||
|
|
||||||
|
export function ThemeSwitcher() {
|
||||||
|
const { setTheme } = useTheme()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<DropdownMenu>
|
||||||
|
<DropdownMenuTrigger asChild>
|
||||||
|
<Button variant="outline" size="icon" className="text-muted-foreground">
|
||||||
|
<Sun className="h-[1.2rem] w-[1.2rem] rotate-0 scale-100 transition-all dark:-rotate-90 dark:scale-0" />
|
||||||
|
<Moon className="absolute h-[1.2rem] w-[1.2rem] rotate-90 scale-0 transition-all dark:rotate-0 dark:scale-100" />
|
||||||
|
<span className="sr-only">Toggle theme</span>
|
||||||
|
</Button>
|
||||||
|
</DropdownMenuTrigger>
|
||||||
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuItem onClick={() => setTheme("light")}>
|
||||||
|
Light
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => setTheme("dark")}>
|
||||||
|
Dark
|
||||||
|
</DropdownMenuItem>
|
||||||
|
<DropdownMenuItem onClick={() => setTheme("system")}>
|
||||||
|
System
|
||||||
|
</DropdownMenuItem>
|
||||||
|
</DropdownMenuContent>
|
||||||
|
</DropdownMenu>
|
||||||
|
)
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user