mirror of
				https://github.com/cfngc4594/monaco-editor-lsp-next.git
				synced 2025-10-31 22:14:05 +00:00 
			
		
		
		
	refactor(app/problems/[id]): restructure components into features directory with index exports
This commit is contained in:
		
							parent
							
								
									787f91b743
								
							
						
					
					
						commit
						8d7d88511e
					
				| @ -1,15 +0,0 @@ | ||||
| interface BotLayoutProps { | ||||
|   children: React.ReactNode; | ||||
| } | ||||
| 
 | ||||
| export default function BotLayout({ | ||||
|   children, | ||||
| }: BotLayoutProps) { | ||||
|   return ( | ||||
|     <div className="flex flex-col h-full"> | ||||
|       <div className="relative flex-1 border-x border-muted"> | ||||
|         {children} | ||||
|       </div> | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
| @ -1,110 +0,0 @@ | ||||
| "use client"; | ||||
| 
 | ||||
| import { toast } from "sonner"; | ||||
| import { useCallback } from "react"; | ||||
| import { useChat } from "@ai-sdk/react"; | ||||
| import { Button } from "@/components/ui/button"; | ||||
| import { useProblem } from "@/hooks/use-problem"; | ||||
| import MdxPreview from "@/components/mdx-preview"; | ||||
| import { Textarea } from "@/components/ui/textarea"; | ||||
| import { BotIcon, SendHorizonal } from "lucide-react"; | ||||
| import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; | ||||
| import { ChatMessageList } from "@/components/ui/chat/chat-message-list"; | ||||
| import { ChatBubble, ChatBubbleMessage } from "@/components/ui/chat/chat-bubble"; | ||||
| import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip"; | ||||
| 
 | ||||
| export default function AiBotPage() { | ||||
|   const { problemId, problem, currentLang, currentValue } = useProblem(); | ||||
|   const { messages, input, handleInputChange, setMessages, handleSubmit } = useChat({ | ||||
|     initialMessages: [ | ||||
|       { | ||||
|         id: problemId, | ||||
|         role: "system", | ||||
|         content: `Problem description:\n${problem.description}`, | ||||
|       }, | ||||
|     ], | ||||
|   }); | ||||
| 
 | ||||
|   const handleFormSubmit = useCallback( | ||||
|     (e: React.FormEvent) => { | ||||
|       e.preventDefault(); | ||||
|       if (!input.trim()) { | ||||
|         toast.error("Input cannot be empty"); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       const currentCodeMessage = { | ||||
|         id: problemId, | ||||
|         role: "system" as const, | ||||
|         content: `Current code:\n\`\`\`${currentLang}\n${currentValue}\n\`\`\``, | ||||
|       }; | ||||
| 
 | ||||
|       setMessages((prev) => [...prev, currentCodeMessage]); | ||||
|       handleSubmit(); | ||||
|     }, | ||||
|     [currentLang, currentValue, handleSubmit, input, problemId, setMessages] | ||||
|   ); | ||||
| 
 | ||||
|   return ( | ||||
|     <div className="flex flex-col h-full bg-background"> | ||||
|       <div className="flex-1 relative"> | ||||
|         {!messages.some((message) => message.role === "user" || message.role === "assistant") && ( | ||||
|           <div className="absolute bottom-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-muted-foreground flex flex-col items-center gap-2"> | ||||
|             <BotIcon /> | ||||
|             <span>Ask Bot</span> | ||||
|             <span className="font-thin text-xs">Powered by Vercel Ai SDK</span> | ||||
|           </div> | ||||
|         )} | ||||
|         <ScrollArea className="[&>[data-radix-scroll-area-viewport]]:max-h-[calc(100vh-238px)] [&>[data-radix-scroll-area-viewport]>div:min-w-0 [&>[data-radix-scroll-area-viewport]>div]:!block"> | ||||
|           <ChatMessageList> | ||||
|             {messages | ||||
|               .filter((message) => message.role === "user" || message.role === "assistant") | ||||
|               .map((message) => ( | ||||
|                 <ChatBubble key={message.id} layout="ai" className="border-b pb-4"> | ||||
|                   <ChatBubbleMessage layout="ai"> | ||||
|                     <MdxPreview source={message.content} /> | ||||
|                   </ChatBubbleMessage> | ||||
|                 </ChatBubble> | ||||
|               ))} | ||||
|           </ChatMessageList> | ||||
|           <ScrollBar orientation="horizontal" /> | ||||
|         </ScrollArea> | ||||
|       </div> | ||||
|       <footer className="h-36 flex flex-none"> | ||||
|         <form onSubmit={handleFormSubmit} className="w-full p-4 pt-0 relative"> | ||||
|           <Textarea | ||||
|             value={input} | ||||
|             onChange={handleInputChange} | ||||
|             onKeyDown={(e) => { | ||||
|               if ((e.ctrlKey || e.metaKey) && e.key === "Enter") { | ||||
|                 e.preventDefault(); | ||||
|                 if (input.trim()) { | ||||
|                   handleFormSubmit(e); | ||||
|                 } else { | ||||
|                   toast.error("Input cannot be empty"); | ||||
|                 } | ||||
|               } | ||||
|             }} | ||||
|             className="h-full bg-muted border-transparent shadow-none rounded-lg" | ||||
|             placeholder="Bot will automatically get your current code" | ||||
|           /> | ||||
|           <TooltipProvider delayDuration={0}> | ||||
|             <Tooltip> | ||||
|               <TooltipTrigger asChild> | ||||
|                 <Button | ||||
|                   type="submit" | ||||
|                   variant="ghost" | ||||
|                   className="absolute bottom-6 right-6 h-6 w-auto px-2" | ||||
|                   aria-label="Send Message" | ||||
|                 > | ||||
|                   <SendHorizonal className="size-4" /> | ||||
|                 </Button> | ||||
|               </TooltipTrigger> | ||||
|               <TooltipContent className="px-2 py-1 text-xs">Ctrl + Enter</TooltipContent> | ||||
|             </Tooltip> | ||||
|           </TooltipProvider> | ||||
|         </form> | ||||
|       </footer> | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
| @ -1,9 +0,0 @@ | ||||
| import { ProblemEditor } from "@/components/problem-editor"; | ||||
| 
 | ||||
| export default function CodePage() { | ||||
|   return ( | ||||
|     <div className="absolute w-full h-full"> | ||||
|       <ProblemEditor /> | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
| @ -1,26 +0,0 @@ | ||||
| "use client"; | ||||
| 
 | ||||
| import { notFound } from "next/navigation"; | ||||
| import { useProblem } from "@/hooks/use-problem"; | ||||
| import ProblemDescriptionFooter from "@/components/features/playground/problem/description/footer"; | ||||
| 
 | ||||
| interface DescriptionLayoutProps { | ||||
|   children: React.ReactNode; | ||||
| } | ||||
| 
 | ||||
| export default function DescriptionLayout({ children }: DescriptionLayoutProps) { | ||||
|   const { problem } = useProblem(); | ||||
| 
 | ||||
|   if (!problem) { | ||||
|     notFound(); | ||||
|   } | ||||
| 
 | ||||
|   return ( | ||||
|     <div className="flex flex-col h-full"> | ||||
|       <main className="relative flex-1 border-x border-muted"> | ||||
|         {children} | ||||
|       </main> | ||||
|       <ProblemDescriptionFooter title={problem.title} /> | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
| @ -1,23 +0,0 @@ | ||||
| "use client"; | ||||
| 
 | ||||
| import { notFound } from "next/navigation"; | ||||
| import { useProblem } from "@/hooks/use-problem"; | ||||
| import MdxPreview from "@/components/mdx-preview"; | ||||
| import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; | ||||
| 
 | ||||
| export default function DescriptionPage() { | ||||
|   const { problem } = useProblem(); | ||||
| 
 | ||||
|   if (!problem) { | ||||
|     notFound(); | ||||
|   } | ||||
| 
 | ||||
|   return ( | ||||
|     <div className="absolute h-full w-full"> | ||||
|       <ScrollArea className="h-full [&>[data-radix-scroll-area-viewport]>div:min-w-0 [&>[data-radix-scroll-area-viewport]>div]:!block bg-background"> | ||||
|         <MdxPreview source={problem.description} className="p-4 md:p-6" /> | ||||
|         <ScrollBar orientation="horizontal" /> | ||||
|       </ScrollArea> | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
| @ -1,28 +0,0 @@ | ||||
| "use client"; | ||||
| 
 | ||||
| import { notFound } from "next/navigation"; | ||||
| import { useProblem } from "@/hooks/use-problem"; | ||||
| import ProblemSolutionFooter from "@/components/features/playground/problem/solution/footer"; | ||||
| 
 | ||||
| interface SolutionsLayoutProps { | ||||
|   children: React.ReactNode; | ||||
| } | ||||
| 
 | ||||
| export default function SolutionsLayout({ | ||||
|   children, | ||||
| }: SolutionsLayoutProps) { | ||||
|   const { problem } = useProblem(); | ||||
| 
 | ||||
|   if (!problem) { | ||||
|     notFound(); | ||||
|   } | ||||
| 
 | ||||
|   return ( | ||||
|     <div className="flex flex-col h-full"> | ||||
|       <div className="relative flex-1 border-x border-muted"> | ||||
|         {children} | ||||
|       </div> | ||||
|       <ProblemSolutionFooter title={problem.title} /> | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
| @ -1,23 +0,0 @@ | ||||
| "use client"; | ||||
| 
 | ||||
| import { notFound } from "next/navigation"; | ||||
| import { useProblem } from "@/hooks/use-problem"; | ||||
| import MdxPreview from "@/components/mdx-preview"; | ||||
| import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; | ||||
| 
 | ||||
| export default function SolutionsPage() { | ||||
|   const { problem } = useProblem(); | ||||
| 
 | ||||
|   if (!problem) { | ||||
|     notFound(); | ||||
|   } | ||||
| 
 | ||||
|   return ( | ||||
|     <div className="absolute h-full w-full"> | ||||
|       <ScrollArea className="h-full [&>[data-radix-scroll-area-viewport]>div:min-w-0 [&>[data-radix-scroll-area-viewport]>div]:!block bg-background"> | ||||
|         <MdxPreview source={problem.solution} className="p-4 md:p-6" /> | ||||
|         <ScrollBar orientation="horizontal" /> | ||||
|       </ScrollArea> | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
							
								
								
									
										123
									
								
								src/app/(app)/problems/[id]/features/bot.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								src/app/(app)/problems/[id]/features/bot.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,123 @@ | ||||
| "use client"; | ||||
| 
 | ||||
| import { toast } from "sonner"; | ||||
| import { | ||||
|   Tooltip, | ||||
|   TooltipContent, | ||||
|   TooltipProvider, | ||||
|   TooltipTrigger, | ||||
| } from "@/components/ui/tooltip"; | ||||
| import { useCallback } from "react"; | ||||
| import { useChat } from "@ai-sdk/react"; | ||||
| import { Button } from "@/components/ui/button"; | ||||
| import { useProblem } from "@/hooks/use-problem"; | ||||
| import MdxPreview from "@/components/mdx-preview"; | ||||
| import { Textarea } from "@/components/ui/textarea"; | ||||
| import { BotIcon, SendHorizonal } from "lucide-react"; | ||||
| import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; | ||||
| import { ChatMessageList } from "@/components/ui/chat/chat-message-list"; | ||||
| import { ChatBubble, ChatBubbleMessage } from "@/components/ui/chat/chat-bubble"; | ||||
| 
 | ||||
| export default function Bot() { | ||||
|   const { problemId, problem, currentLang, currentValue } = useProblem(); | ||||
|   const { messages, input, handleInputChange, setMessages, handleSubmit } = useChat({ | ||||
|     initialMessages: [ | ||||
|       { | ||||
|         id: problemId, | ||||
|         role: "system", | ||||
|         content: `Problem description:\n${problem.description}`, | ||||
|       }, | ||||
|     ], | ||||
|   }); | ||||
| 
 | ||||
|   const handleFormSubmit = useCallback( | ||||
|     (e: React.FormEvent) => { | ||||
|       e.preventDefault(); | ||||
|       if (!input.trim()) { | ||||
|         toast.error("Input cannot be empty"); | ||||
|         return; | ||||
|       } | ||||
| 
 | ||||
|       const currentCodeMessage = { | ||||
|         id: problemId, | ||||
|         role: "system" as const, | ||||
|         content: `Current code:\n\`\`\`${currentLang}\n${currentValue}\n\`\`\``, | ||||
|       }; | ||||
| 
 | ||||
|       setMessages((prev) => [...prev, currentCodeMessage]); | ||||
|       handleSubmit(); | ||||
|     }, | ||||
|     [currentLang, currentValue, handleSubmit, input, problemId, setMessages] | ||||
|   ); | ||||
| 
 | ||||
|   return ( | ||||
|     <div className="flex flex-col h-full"> | ||||
|       <div className="relative flex-1 border-x border-muted"> | ||||
|         <div className="flex flex-col h-full bg-background"> | ||||
|           <div className="flex-1 relative"> | ||||
|             {!messages.some( | ||||
|               (message) => message.role === "user" || message.role === "assistant" | ||||
|             ) && ( | ||||
|               <div className="absolute bottom-1/2 left-1/2 -translate-x-1/2 -translate-y-1/2 text-muted-foreground flex flex-col items-center gap-2"> | ||||
|                 <BotIcon /> | ||||
|                 <span>Ask Bot</span> | ||||
|                 <span className="font-thin text-xs">Powered by Vercel Ai SDK</span> | ||||
|               </div> | ||||
|             )} | ||||
|             <ScrollArea className="[&>[data-radix-scroll-area-viewport]]:max-h-[calc(100vh-238px)] [&>[data-radix-scroll-area-viewport]>div:min-w-0 [&>[data-radix-scroll-area-viewport]>div]:!block"> | ||||
|               <ChatMessageList> | ||||
|                 {messages | ||||
|                   .filter( | ||||
|                     (message) => message.role === "user" || message.role === "assistant" | ||||
|                   ) | ||||
|                   .map((message) => ( | ||||
|                     <ChatBubble key={message.id} layout="ai" className="border-b pb-4"> | ||||
|                       <ChatBubbleMessage layout="ai"> | ||||
|                         <MdxPreview source={message.content} /> | ||||
|                       </ChatBubbleMessage> | ||||
|                     </ChatBubble> | ||||
|                   ))} | ||||
|               </ChatMessageList> | ||||
|               <ScrollBar orientation="horizontal" /> | ||||
|             </ScrollArea> | ||||
|           </div> | ||||
|           <footer className="h-36 flex flex-none"> | ||||
|             <form onSubmit={handleFormSubmit} className="w-full p-4 pt-0 relative"> | ||||
|               <Textarea | ||||
|                 value={input} | ||||
|                 onChange={handleInputChange} | ||||
|                 onKeyDown={(e) => { | ||||
|                   if ((e.ctrlKey || e.metaKey) && e.key === "Enter") { | ||||
|                     e.preventDefault(); | ||||
|                     if (input.trim()) { | ||||
|                       handleFormSubmit(e); | ||||
|                     } else { | ||||
|                       toast.error("Input cannot be empty"); | ||||
|                     } | ||||
|                   } | ||||
|                 }} | ||||
|                 className="h-full bg-muted border-transparent shadow-none rounded-lg" | ||||
|                 placeholder="Bot will automatically get your current code" | ||||
|               /> | ||||
|               <TooltipProvider delayDuration={0}> | ||||
|                 <Tooltip> | ||||
|                   <TooltipTrigger asChild> | ||||
|                     <Button | ||||
|                       type="submit" | ||||
|                       variant="ghost" | ||||
|                       className="absolute bottom-6 right-6 h-6 w-auto px-2" | ||||
|                       aria-label="Send Message" | ||||
|                     > | ||||
|                       <SendHorizonal className="size-4" /> | ||||
|                     </Button> | ||||
|                   </TooltipTrigger> | ||||
|                   <TooltipContent className="px-2 py-1 text-xs">Ctrl + Enter</TooltipContent> | ||||
|                 </Tooltip> | ||||
|               </TooltipProvider> | ||||
|             </form> | ||||
|           </footer> | ||||
|         </div> | ||||
|       </div> | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
| @ -1,16 +1,15 @@ | ||||
| import { ProblemEditor } from "@/components/problem-editor"; | ||||
| import { WorkspaceEditorHeader } from "@/components/features/playground/workspace/editor/components/header"; | ||||
| import { WorkspaceEditorFooter } from "@/components/features/playground/workspace/editor/components/footer"; | ||||
| 
 | ||||
| interface CodeLayoutProps { | ||||
|   children: React.ReactNode; | ||||
| } | ||||
| 
 | ||||
| export default function CodeLayout({ children }: CodeLayoutProps) { | ||||
| export default function Code() { | ||||
|   return ( | ||||
|     <div className="flex flex-col h-full"> | ||||
|       <WorkspaceEditorHeader className="border-b border-x border-muted bg-background" /> | ||||
|       <div className="relative flex-1 border-x border-muted"> | ||||
|         {children} | ||||
|         <div className="absolute w-full h-full"> | ||||
|           <ProblemEditor /> | ||||
|         </div> | ||||
|       </div> | ||||
|       <WorkspaceEditorFooter /> | ||||
|     </div> | ||||
							
								
								
									
										29
									
								
								src/app/(app)/problems/[id]/features/description.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/app/(app)/problems/[id]/features/description.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| "use client"; | ||||
| 
 | ||||
| import { notFound } from "next/navigation"; | ||||
| import { useProblem } from "@/hooks/use-problem"; | ||||
| import MdxPreview from "@/components/mdx-preview"; | ||||
| import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; | ||||
| import ProblemDescriptionFooter from "@/components/features/playground/problem/description/footer"; | ||||
| 
 | ||||
| export default function Description() { | ||||
|   const { problem } = useProblem(); | ||||
| 
 | ||||
|   if (!problem) { | ||||
|     notFound(); | ||||
|   } | ||||
| 
 | ||||
|   return ( | ||||
|     <div className="flex flex-col h-full"> | ||||
|       <div className="relative flex-1 border-x border-muted"> | ||||
|         <div className="absolute h-full w-full"> | ||||
|           <ScrollArea className="h-full [&>[data-radix-scroll-area-viewport]>div:min-w-0 [&>[data-radix-scroll-area-viewport]>div]:!block bg-background"> | ||||
|             <MdxPreview source={problem.description} className="p-4 md:p-6" /> | ||||
|             <ScrollBar orientation="horizontal" /> | ||||
|           </ScrollArea> | ||||
|         </div> | ||||
|       </div> | ||||
|       <ProblemDescriptionFooter title={problem.title} /> | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
							
								
								
									
										7
									
								
								src/app/(app)/problems/[id]/features/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/app/(app)/problems/[id]/features/index.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,7 @@ | ||||
| export { default as Bot } from "./bot"; | ||||
| export { default as Code } from "./code"; | ||||
| export { default as Description } from "./description"; | ||||
| export { default as Solutions } from "./solutions"; | ||||
| export { default as Submissions } from "./submissions"; | ||||
| export { default as Testcase } from "./testcase"; | ||||
| export { default as TestResult } from "./test-result"; | ||||
							
								
								
									
										29
									
								
								src/app/(app)/problems/[id]/features/solutions.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/app/(app)/problems/[id]/features/solutions.tsx
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| "use client"; | ||||
| 
 | ||||
| import { notFound } from "next/navigation"; | ||||
| import { useProblem } from "@/hooks/use-problem"; | ||||
| import MdxPreview from "@/components/mdx-preview"; | ||||
| import { ScrollArea, ScrollBar } from "@/components/ui/scroll-area"; | ||||
| import ProblemSolutionFooter from "@/components/features/playground/problem/solution/footer"; | ||||
| 
 | ||||
| export default function Solutions() { | ||||
|   const { problem } = useProblem(); | ||||
| 
 | ||||
|   if (!problem) { | ||||
|     notFound(); | ||||
|   } | ||||
| 
 | ||||
|   return ( | ||||
|     <div className="flex flex-col h-full"> | ||||
|       <div className="relative flex-1 border-x border-muted"> | ||||
|         <div className="absolute h-full w-full"> | ||||
|           <ScrollArea className="h-full [&>[data-radix-scroll-area-viewport]>div:min-w-0 [&>[data-radix-scroll-area-viewport]>div]:!block bg-background"> | ||||
|             <MdxPreview source={problem.solution} className="p-4 md:p-6" /> | ||||
|             <ScrollBar orientation="horizontal" /> | ||||
|           </ScrollArea> | ||||
|         </div> | ||||
|       </div> | ||||
|       <ProblemSolutionFooter title={problem.title} /> | ||||
|     </div> | ||||
|   ); | ||||
| } | ||||
| @ -1,3 +1,3 @@ | ||||
| export default function SubmissionsPage() { | ||||
| export default function Submissions() { | ||||
|   return <div className="h-full px-4">Submissions</div>; | ||||
| } | ||||
| @ -1,3 +1,3 @@ | ||||
| export default function TestResultPage() { | ||||
| export default function TestResult() { | ||||
|   return <div className="h-full px-4">Test Result</div>; | ||||
| } | ||||
| @ -1,3 +1,3 @@ | ||||
| export default function TestcasePage() { | ||||
| export default function Testcase() { | ||||
|   return <div className="h-full px-4">Testcase</div>; | ||||
| } | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user