|
1 | | -"use client" |
2 | | - |
3 | | -import { Suspense, useMemo, useState } from "react" |
4 | | - |
5 | | -import { Button } from "@/components/ui/button" |
6 | | -import { InstructionsWizard } from "@/components/instructions-wizard" |
7 | | -import { AnimatedBackground } from "@/components/AnimatedBackground" |
8 | | -import { getHomeMainClasses } from "@/lib/utils" |
9 | | -import { ANALYTICS_EVENTS } from "@/lib/analytics-events" |
10 | | -import { track } from "@/lib/mixpanel" |
11 | | -import type { DataQuestionSource, FileOutputConfig } from "@/types/wizard" |
12 | | -import { Github } from "lucide-react" |
13 | | -import Link from "next/link" |
14 | | -import { useSearchParams } from "next/navigation" |
15 | | - |
16 | | -import filesData from "@/data/files.json" |
17 | | -import { buildFileOptionsFromQuestion } from "@/lib/wizard-utils" |
18 | | - |
19 | | -const fileQuestionSet = filesData as DataQuestionSource[] |
20 | | -const fileQuestion = fileQuestionSet[0] ?? null |
21 | | -const fileOptionsFromData = buildFileOptionsFromQuestion(fileQuestion) |
22 | | - |
23 | | -export default function NewInstructionsPage() { |
24 | | - return ( |
25 | | - <Suspense fallback={<LoadingFallback />}> |
26 | | - <NewInstructionsPageContent /> |
27 | | - </Suspense> |
28 | | - ) |
29 | | -} |
30 | | - |
31 | | -function NewInstructionsPageContent() { |
32 | | - const searchParams = useSearchParams() |
33 | | - const [showWizard, setShowWizard] = useState(false) |
34 | | - const [selectedFileId, setSelectedFileId] = useState<string | null>(null) |
35 | | - |
36 | | - const fileOptions = useMemo(() => fileOptionsFromData, []) |
37 | | - const preferredStackId = searchParams.get("stack")?.toLowerCase() ?? null |
38 | | - |
39 | | - const handleFileCtaClick = (file: FileOutputConfig) => { |
40 | | - setSelectedFileId(file.id) |
41 | | - setShowWizard(true) |
42 | | - track(ANALYTICS_EVENTS.CREATE_INSTRUCTIONS_FILE, { |
43 | | - fileId: file.id, |
44 | | - fileLabel: file.label, |
45 | | - }) |
46 | | - } |
47 | | - |
48 | | - const handleWizardClose = () => { |
49 | | - setShowWizard(false) |
50 | | - setSelectedFileId(null) |
51 | | - } |
52 | | - |
53 | | - return ( |
54 | | - <div className="relative min-h-screen overflow-hidden bg-background text-foreground"> |
55 | | - <AnimatedBackground /> |
56 | | - <div className="relative z-10 flex min-h-screen flex-col"> |
57 | | - {/* Top utility bar */} |
58 | | - <div |
59 | | - className={`absolute inset-x-0 top-4 flex items-center px-6 sm:px-8 lg:px-12 ${showWizard ? "justify-end" : "justify-between"}`} |
60 | | - > |
61 | | - {!showWizard ? ( |
62 | | - <> |
63 | | - <Link href="/" className="text-lg font-semibold tracking-tight text-foreground md:text-xl"> |
64 | | - DevContext |
65 | | - </Link> |
66 | | - <Link href="https://github.com/spivx/devcontext" target="_blank"> |
67 | | - <Button variant="outline" size="sm"> |
68 | | - <Github className="mr-2 h-4 w-4" /> |
69 | | - GitHub |
70 | | - </Button> |
71 | | - </Link> |
72 | | - </> |
73 | | - ) : ( |
74 | | - <Link href="https://github.com/spivx/devcontext" target="_blank"> |
75 | | - <Button variant="outline" size="sm"> |
76 | | - <Github className="mr-2 h-4 w-4" /> |
77 | | - GitHub |
78 | | - </Button> |
79 | | - </Link> |
80 | | - )} |
81 | | - </div> |
82 | | - |
83 | | - {/* Hero Section */} |
84 | | - <main className={getHomeMainClasses(showWizard)}> |
85 | | - {showWizard && selectedFileId ? ( |
86 | | - <InstructionsWizard |
87 | | - selectedFileId={selectedFileId} |
88 | | - onClose={handleWizardClose} |
89 | | - initialStackId={preferredStackId} |
90 | | - /> |
91 | | - ) : ( |
92 | | - <> |
93 | | - <div className="space-y-6"> |
94 | | - <div className="space-y-4"> |
95 | | - <h1 className="text-3xl font-bold">Start a new instructions project</h1> |
96 | | - <p className="mx-auto max-w-2xl text-base leading-relaxed text-muted-foreground md:text-lg"> |
97 | | - Choose the file preset that matches what you need. The wizard will open with targeted questions and save progress as you go. |
98 | | - </p> |
99 | | - <div className="mx-auto max-w-2xl text-left text-sm text-muted-foreground/90 md:text-base"> |
100 | | - <ul className="list-disc space-y-2 pl-5"> |
101 | | - <li>Pick a preset to load stack, architecture, and workflow prompts.</li> |
102 | | - <li>Answer or skip questions — you can revisit any step before exporting.</li> |
103 | | - <li>Download the generated file once every section shows as complete.</li> |
104 | | - </ul> |
105 | | - </div> |
106 | | - </div> |
107 | | - |
108 | | - {/* File type CTAs */} |
109 | | - <div className="pt-6"> |
110 | | - <div className="flex flex-wrap items-center justify-center gap-5"> |
111 | | - {fileOptions.map((file) => { |
112 | | - return ( |
113 | | - <button |
114 | | - key={file.id} |
115 | | - type="button" |
116 | | - className="group inline-flex h-32 w-32 items-center justify-center rounded-full border border-border/60 bg-gradient-to-br from-card/95 via-card/90 to-card/80 px-6 text-sm font-medium text-foreground text-center shadow-sm ring-offset-background transition-all duration-200 hover:-translate-y-0.5 hover:border-primary/30 hover:shadow-lg focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/80" |
117 | | - onClick={() => handleFileCtaClick(file)} |
118 | | - aria-label={`Create ${file.label}`} |
119 | | - > |
120 | | - <span className="break-words text-sm font-semibold leading-tight text-foreground transition-colors duration-200 group-hover:text-primary/90"> |
121 | | - {file.filename ?? file.label} |
122 | | - </span> |
123 | | - </button> |
124 | | - ) |
125 | | - })} |
126 | | - </div> |
127 | | - </div> |
128 | | - </div> |
129 | | - </> |
130 | | - )} |
131 | | - </main> |
132 | | - </div> |
133 | | - </div> |
134 | | - ) |
| 1 | +import type { Metadata } from "next" |
| 2 | +import { redirect } from "next/navigation" |
| 3 | + |
| 4 | +const title = "Launch the DevContext Wizard" |
| 5 | +const description = |
| 6 | + "Start a guided flow to assemble AI-ready coding instruction files. Pick your stack, customize conventions, and export Copilot, Cursor, or agents guidelines in minutes." |
| 7 | + |
| 8 | +export const metadata: Metadata = { |
| 9 | + title, |
| 10 | + description, |
| 11 | + alternates: { |
| 12 | + canonical: "/new", |
| 13 | + }, |
| 14 | + openGraph: { |
| 15 | + title, |
| 16 | + description, |
| 17 | + url: "/new", |
| 18 | + type: "website", |
| 19 | + siteName: "DevContext", |
| 20 | + images: [ |
| 21 | + { |
| 22 | + url: "/og-image.png", |
| 23 | + width: 1200, |
| 24 | + height: 630, |
| 25 | + alt: "DevContext wizard interface preview", |
| 26 | + }, |
| 27 | + ], |
| 28 | + }, |
| 29 | + twitter: { |
| 30 | + card: "summary_large_image", |
| 31 | + title, |
| 32 | + description, |
| 33 | + images: ["/og-image.png"], |
| 34 | + }, |
135 | 35 | } |
136 | 36 |
|
137 | | -function LoadingFallback() { |
138 | | - return ( |
139 | | - <div className="flex min-h-screen items-center justify-center bg-background text-sm text-muted-foreground"> |
140 | | - Loading wizard… |
141 | | - </div> |
142 | | - ) |
| 37 | +export default function NewPage() { |
| 38 | + redirect(`/new/stack`) |
143 | 39 | } |
0 commit comments