Enhanced code review and frontend validation with intelligent triggers: Code Review Agent Enhancement: - Added Sequential Thinking MCP integration for complex issues - Triggers on 2+ rejections or 3+ critical issues - New escalation format with root cause analysis - Comprehensive solution strategies with trade-off evaluation - Educational feedback to break rejection cycles - Files: .claude/agents/code-review.md (+308 lines) - Docs: CODE_REVIEW_ST_ENHANCEMENT.md, CODE_REVIEW_ST_TESTING.md Frontend Design Skill Enhancement: - Automatic invocation for ANY UI change - Comprehensive validation checklist (200+ checkpoints) - 8 validation categories (visual, interactive, responsive, a11y, etc.) - 3 validation levels (quick, standard, comprehensive) - Integration with code review workflow - Files: .claude/skills/frontend-design/SKILL.md (+120 lines) - Docs: UI_VALIDATION_CHECKLIST.md (462 lines), AUTOMATIC_VALIDATION_ENHANCEMENT.md (587 lines) Settings Optimization: - Repaired .claude/settings.local.json (fixed m365 pattern) - Reduced permissions from 49 to 33 (33% reduction) - Removed duplicates, sorted alphabetically - Created SETTINGS_PERMISSIONS.md documentation Checkpoint Command Enhancement: - Dual checkpoint system (git + database) - Saves session context to API for cross-machine recall - Includes git metadata in database context - Files: .claude/commands/checkpoint.md (+139 lines) Decision Rationale: - Sequential Thinking MCP breaks rejection cycles by identifying root causes - Automatic frontend validation catches UI issues before code review - Dual checkpoints enable complete project memory across machines - Settings optimization improves maintainability Total: 1,200+ lines of documentation and enhancements Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
552 lines
27 KiB
Plaintext
552 lines
27 KiB
Plaintext
1→import { useState } from 'react'
|
|
2→import { useNavigate } from 'react-router-dom'
|
|
3→import { useProjectStore } from '../stores/projectStore'
|
|
4→import { ArrowLeft, ArrowRight, Check, Loader2, Plus, X } from 'lucide-react'
|
|
5→
|
|
6→interface TechStack {
|
|
7→ frontend: string[]
|
|
8→ backend: string[]
|
|
9→ database: string[]
|
|
10→ other: string[]
|
|
11→}
|
|
12→
|
|
13→interface DesignPreferences {
|
|
14→ theme: 'light' | 'dark' | 'system'
|
|
15→ primaryColor: string
|
|
16→ style: 'minimal' | 'modern' | 'classic'
|
|
17→ designMode: 'mvp' | 'finished'
|
|
18→}
|
|
19→
|
|
20→export default function ProjectWizard() {
|
|
21→ const navigate = useNavigate()
|
|
22→ const { createProject, isLoading } = useProjectStore()
|
|
23→ const [step, setStep] = useState(1)
|
|
24→ const [formData, setFormData] = useState({
|
|
25→ name: '',
|
|
26→ description: '',
|
|
27→ complexity: 'medium',
|
|
28→ path: '',
|
|
29→ })
|
|
30→ const [techStack, setTechStack] = useState<TechStack>({
|
|
31→ frontend: [],
|
|
32→ backend: [],
|
|
33→ database: [],
|
|
34→ other: [],
|
|
35→ })
|
|
36→ const [features, setFeatures] = useState<string[]>([])
|
|
37→ const [newFeature, setNewFeature] = useState('')
|
|
38→ const [designPreferences, setDesignPreferences] = useState<DesignPreferences>({
|
|
39→ theme: 'dark',
|
|
40→ primaryColor: '#0d9488',
|
|
41→ style: 'modern',
|
|
42→ designMode: 'mvp',
|
|
43→ })
|
|
44→ const [errors, setErrors] = useState<{ name?: string }>({})
|
|
45→ const [touched, setTouched] = useState<{ name?: boolean }>({})
|
|
46→
|
|
47→ const totalSteps = 5
|
|
48→
|
|
49→ const validateName = (name: string): string | undefined => {
|
|
50→ if (!name.trim()) {
|
|
51→ return 'Project name is required'
|
|
52→ }
|
|
53→ if (name.trim().length < 2) {
|
|
54→ return 'Project name must be at least 2 characters'
|
|
55→ }
|
|
56→ if (name.trim().length > 100) {
|
|
57→ return 'Project name must be less than 100 characters'
|
|
58→ }
|
|
59→ return undefined
|
|
60→ }
|
|
61→
|
|
62→ const handleNext = () => {
|
|
63→ if (step === 1) {
|
|
64→ const nameError = validateName(formData.name)
|
|
65→ setTouched({ ...touched, name: true })
|
|
66→ setErrors({ ...errors, name: nameError })
|
|
67→ if (nameError) return
|
|
68→ }
|
|
69→ if (step < totalSteps) {
|
|
70→ setStep(step + 1)
|
|
71→ }
|
|
72→ }
|
|
73→
|
|
74→ const handleBack = () => {
|
|
75→ if (step > 1) {
|
|
76→ setStep(step - 1)
|
|
77→ }
|
|
78→ }
|
|
79→
|
|
80→ const handleSubmit = async () => {
|
|
81→ try {
|
|
82→ await createProject({
|
|
83→ name: formData.name,
|
|
84→ description: formData.description,
|
|
85→ design_mode: designPreferences.designMode,
|
|
86→ })
|
|
87→ navigate('/')
|
|
88→ } catch (error) {
|
|
89→ console.error('Failed to create project:', error)
|
|
90→ }
|
|
91→ }
|
|
92→
|
|
93→ return (
|
|
94→ <div className="max-w-2xl mx-auto">
|
|
95→ {/* Progress */}
|
|
96→ <div className="mb-8">
|
|
97→ <div className="flex items-center justify-between mb-2">
|
|
98→ <span className="text-sm text-slate-400">
|
|
99→ Step {step} of {totalSteps}
|
|
100→ </span>
|
|
101→ <span className="text-sm text-slate-400">
|
|
102→ {Math.round((step / totalSteps) * 100)}% complete
|
|
103→ </span>
|
|
104→ </div>
|
|
105→ <div className="w-full h-2 bg-slate-700 rounded-full overflow-hidden">
|
|
106→ <div
|
|
107→ className="h-full bg-accent transition-all duration-300"
|
|
108→ style={{ width: `${(step / totalSteps) * 100}%` }}
|
|
109→ />
|
|
110→ </div>
|
|
111→ </div>
|
|
112→
|
|
113→ {/* Step Content */}
|
|
114→ <div className="bg-slate-800 rounded-lg p-6 border border-slate-700 mb-6">
|
|
115→ {step === 1 && (
|
|
116→ <div>
|
|
117→ <h2 className="text-xl font-semibold mb-4">Project Basics</h2>
|
|
118→ <div className="space-y-4">
|
|
119→ <div>
|
|
120→ <label className="block text-sm font-medium mb-2">
|
|
121→ Project Name *
|
|
122→ </label>
|
|
123→ <input
|
|
124→ type="text"
|
|
125→ value={formData.name}
|
|
126→ onChange={(e) => {
|
|
127→ const newName = e.target.value
|
|
128→ setFormData({ ...formData, name: newName })
|
|
129→ if (touched.name) {
|
|
130→ setErrors({ ...errors, name: validateName(newName) })
|
|
131→ }
|
|
132→ }}
|
|
133→ onBlur={() => {
|
|
134→ setTouched({ ...touched, name: true })
|
|
135→ setErrors({ ...errors, name: validateName(formData.name) })
|
|
136→ }}
|
|
137→ placeholder="My Awesome Project"
|
|
138→ className={`w-full px-4 py-2 bg-slate-700 border rounded-lg focus:outline-none focus:ring-2 ${
|
|
139→ touched.name && errors.name
|
|
140→ ? 'border-red-500 focus:ring-red-500'
|
|
141→ : 'border-slate-600 focus:ring-accent'
|
|
142→ }`}
|
|
143→ />
|
|
144→ {touched.name && errors.name && (
|
|
145→ <p className="mt-1 text-sm text-red-500">{errors.name}</p>
|
|
146→ )}
|
|
147→ </div>
|
|
148→ <div>
|
|
149→ <label className="block text-sm font-medium mb-2">
|
|
150→ Description
|
|
151→ </label>
|
|
152→ <textarea
|
|
153→ value={formData.description}
|
|
154→ onChange={(e) =>
|
|
155→ setFormData({ ...formData, description: e.target.value })
|
|
156→ }
|
|
157→ placeholder="A brief description of your project..."
|
|
158→ rows={3}
|
|
159→ className="w-full px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-accent"
|
|
160→ />
|
|
161→ </div>
|
|
162→ </div>
|
|
163→ </div>
|
|
164→ )}
|
|
165→
|
|
166→ {step === 2 && (
|
|
167→ <div>
|
|
168→ <h2 className="text-xl font-semibold mb-4">Technology Stack</h2>
|
|
169→ <div className="space-y-4">
|
|
170→ {/* Frontend Technologies */}
|
|
171→ <div>
|
|
172→ <label className="block text-sm font-medium mb-2">Frontend</label>
|
|
173→ <div className="flex flex-wrap gap-2">
|
|
174→ {['React', 'Vue', 'Angular', 'Svelte', 'Next.js', 'HTML/CSS'].map((tech) => (
|
|
175→ <button
|
|
176→ key={tech}
|
|
177→ onClick={() => {
|
|
178→ const current = techStack.frontend
|
|
179→ setTechStack({
|
|
180→ ...techStack,
|
|
181→ frontend: current.includes(tech)
|
|
182→ ? current.filter((t) => t !== tech)
|
|
183→ : [...current, tech],
|
|
184→ })
|
|
185→ }}
|
|
186→ className={`px-3 py-1.5 rounded-lg border text-sm transition-colors ${
|
|
187→ techStack.frontend.includes(tech)
|
|
188→ ? 'border-accent bg-accent/20 text-accent'
|
|
189→ : 'border-slate-600 hover:border-slate-500'
|
|
190→ }`}
|
|
191→ >
|
|
192→ {tech}
|
|
193→ </button>
|
|
194→ ))}
|
|
195→ </div>
|
|
196→ </div>
|
|
197→ {/* Backend Technologies */}
|
|
198→ <div>
|
|
199→ <label className="block text-sm font-medium mb-2">Backend</label>
|
|
200→ <div className="flex flex-wrap gap-2">
|
|
201→ {['Python', 'Node.js', 'Go', 'Rust', 'Java', 'Ruby', 'PHP'].map((tech) => (
|
|
202→ <button
|
|
203→ key={tech}
|
|
204→ onClick={() => {
|
|
205→ const current = techStack.backend
|
|
206→ setTechStack({
|
|
207→ ...techStack,
|
|
208→ backend: current.includes(tech)
|
|
209→ ? current.filter((t) => t !== tech)
|
|
210→ : [...current, tech],
|
|
211→ })
|
|
212→ }}
|
|
213→ className={`px-3 py-1.5 rounded-lg border text-sm transition-colors ${
|
|
214→ techStack.backend.includes(tech)
|
|
215→ ? 'border-accent bg-accent/20 text-accent'
|
|
216→ : 'border-slate-600 hover:border-slate-500'
|
|
217→ }`}
|
|
218→ >
|
|
219→ {tech}
|
|
220→ </button>
|
|
221→ ))}
|
|
222→ </div>
|
|
223→ </div>
|
|
224→ {/* Database */}
|
|
225→ <div>
|
|
226→ <label className="block text-sm font-medium mb-2">Database</label>
|
|
227→ <div className="flex flex-wrap gap-2">
|
|
228→ {['PostgreSQL', 'MySQL', 'SQLite', 'MongoDB', 'Redis', 'Firebase'].map((tech) => (
|
|
229→ <button
|
|
230→ key={tech}
|
|
231→ onClick={() => {
|
|
232→ const current = techStack.database
|
|
233→ setTechStack({
|
|
234→ ...techStack,
|
|
235→ database: current.includes(tech)
|
|
236→ ? current.filter((t) => t !== tech)
|
|
237→ : [...current, tech],
|
|
238→ })
|
|
239→ }}
|
|
240→ className={`px-3 py-1.5 rounded-lg border text-sm transition-colors ${
|
|
241→ techStack.database.includes(tech)
|
|
242→ ? 'border-accent bg-accent/20 text-accent'
|
|
243→ : 'border-slate-600 hover:border-slate-500'
|
|
244→ }`}
|
|
245→ >
|
|
246→ {tech}
|
|
247→ </button>
|
|
248→ ))}
|
|
249→ </div>
|
|
250→ </div>
|
|
251→ </div>
|
|
252→ </div>
|
|
253→ )}
|
|
254→
|
|
255→ {step === 3 && (
|
|
256→ <div>
|
|
257→ <h2 className="text-xl font-semibold mb-4">Features</h2>
|
|
258→ <p className="text-slate-400 text-sm mb-4">
|
|
259→ Add the main features you want your project to have.
|
|
260→ </p>
|
|
261→ <div className="space-y-3">
|
|
262→ {/* Add feature input */}
|
|
263→ <div className="flex gap-2">
|
|
264→ <input
|
|
265→ type="text"
|
|
266→ value={newFeature}
|
|
267→ onChange={(e) => setNewFeature(e.target.value)}
|
|
268→ onKeyDown={(e) => {
|
|
269→ if (e.key === 'Enter' && newFeature.trim()) {
|
|
270→ setFeatures([...features, newFeature.trim()])
|
|
271→ setNewFeature('')
|
|
272→ }
|
|
273→ }}
|
|
274→ placeholder="Enter a feature..."
|
|
275→ className="flex-1 px-4 py-2 bg-slate-700 border border-slate-600 rounded-lg focus:outline-none focus:ring-2 focus:ring-accent"
|
|
276→ />
|
|
277→ <button
|
|
278→ onClick={() => {
|
|
279→ if (newFeature.trim()) {
|
|
280→ setFeatures([...features, newFeature.trim()])
|
|
281→ setNewFeature('')
|
|
282→ }
|
|
283→ }}
|
|
284→ disabled={!newFeature.trim()}
|
|
285→ className="btn-accent disabled:opacity-50 disabled:cursor-not-allowed"
|
|
286→ >
|
|
287→ <Plus size={18} />
|
|
288→ </button>
|
|
289→ </div>
|
|
290→ {/* Feature list */}
|
|
291→ <div className="space-y-2">
|
|
292→ {features.length === 0 ? (
|
|
293→ <p className="text-slate-500 italic text-sm">No features added yet</p>
|
|
294→ ) : (
|
|
295→ features.map((feature, index) => (
|
|
296→ <div
|
|
297→ key={index}
|
|
298→ className="flex items-center justify-between p-3 bg-slate-700 rounded-lg"
|
|
299→ >
|
|
300→ <span>{feature}</span>
|
|
301→ <button
|
|
302→ onClick={() => setFeatures(features.filter((_, i) => i !== index))}
|
|
303→ className="text-slate-400 hover:text-destructive transition-colors"
|
|
304→ >
|
|
305→ <X size={16} />
|
|
306→ </button>
|
|
307→ </div>
|
|
308→ ))
|
|
309→ )}
|
|
310→ </div>
|
|
311→ </div>
|
|
312→ </div>
|
|
313→ )}
|
|
314→
|
|
315→ {step === 4 && (
|
|
316→ <div>
|
|
317→ <h2 className="text-xl font-semibold mb-4">Design Preferences</h2>
|
|
318→ <div className="space-y-4">
|
|
319→ {/* Design Mode - MVP vs Finished */}
|
|
320→ <div>
|
|
321→ <label className="block text-sm font-medium mb-2">Design Mode</label>
|
|
322→ <p className="text-sm text-slate-400 mb-3">
|
|
323→ Choose how strictly design requirements should be applied.
|
|
324→ </p>
|
|
325→ <div className="space-y-2">
|
|
326→ <button
|
|
327→ onClick={() => setDesignPreferences({ ...designPreferences, designMode: 'mvp' })}
|
|
328→ className={`w-full p-4 rounded-lg border text-left transition-colors ${
|
|
329→ designPreferences.designMode === 'mvp'
|
|
330→ ? 'border-accent bg-accent/10'
|
|
331→ : 'border-slate-600 hover:border-slate-500'
|
|
332→ }`}
|
|
333→ >
|
|
334→ <div className="flex items-center justify-between">
|
|
335→ <p className="font-semibold">MVP Mode</p>
|
|
336→ {designPreferences.designMode === 'mvp' && (
|
|
337→ <span className="text-xs bg-accent/20 text-accent px-2 py-1 rounded">Selected</span>
|
|
338→ )}
|
|
339→ </div>
|
|
340→ <p className="text-sm text-slate-400 mt-1">
|
|
341→ Functional drafts are acceptable. Focus on getting features working first,
|
|
342→ with polished design coming later. Best for rapid prototyping and early development.
|
|
343→ </p>
|
|
344→ </button>
|
|
345→ <button
|
|
346→ onClick={() => setDesignPreferences({ ...designPreferences, designMode: 'finished' })}
|
|
347→ className={`w-full p-4 rounded-lg border text-left transition-colors ${
|
|
348→ designPreferences.designMode === 'finished'
|
|
349→ ? 'border-accent bg-accent/10'
|
|
350→ : 'border-slate-600 hover:border-slate-500'
|
|
351→ }`}
|
|
352→ >
|
|
353→ <div className="flex items-center justify-between">
|
|
354→ <p className="font-semibold">Finished Mode</p>
|
|
355→ {designPreferences.designMode === 'finished' && (
|
|
356→ <span className="text-xs bg-accent/20 text-accent px-2 py-1 rounded">Selected</span>
|
|
357→ )}
|
|
358→ </div>
|
|
359→ <p className="text-sm text-slate-400 mt-1">
|
|
360→ Stricter design requirements apply. All frontend work must meet polish standards
|
|
361→ with proper typography, color, motion, and spatial composition. Best for production-ready builds.
|
|
362→ </p>
|
|
363→ </button>
|
|
364→ </div>
|
|
365→ </div>
|
|
366→ {/* Theme */}
|
|
367→ <div>
|
|
368→ <label className="block text-sm font-medium mb-2">Theme</label>
|
|
369→ <div className="flex gap-3">
|
|
370→ {(['light', 'dark', 'system'] as const).map((theme) => (
|
|
371→ <button
|
|
372→ key={theme}
|
|
373→ onClick={() => setDesignPreferences({ ...designPreferences, theme })}
|
|
374→ className={`flex-1 p-3 rounded-lg border text-center capitalize transition-colors ${
|
|
375→ designPreferences.theme === theme
|
|
376→ ? 'border-accent bg-accent/10'
|
|
377→ : 'border-slate-600 hover:border-slate-500'
|
|
378→ }`}
|
|
379→ >
|
|
380→ {theme}
|
|
381→ </button>
|
|
382→ ))}
|
|
383→ </div>
|
|
384→ </div>
|
|
385→ {/* Style */}
|
|
386→ <div>
|
|
387→ <label className="block text-sm font-medium mb-2">UI Style</label>
|
|
388→ <div className="space-y-2">
|
|
389→ {(['minimal', 'modern', 'classic'] as const).map((style) => (
|
|
390→ <button
|
|
391→ key={style}
|
|
392→ onClick={() => setDesignPreferences({ ...designPreferences, style })}
|
|
393→ className={`w-full p-3 rounded-lg border text-left transition-colors ${
|
|
394→ designPreferences.style === style
|
|
395→ ? 'border-accent bg-accent/10'
|
|
396→ : 'border-slate-600 hover:border-slate-500'
|
|
397→ }`}
|
|
398→ >
|
|
399→ <p className="font-semibold capitalize">{style}</p>
|
|
400→ <p className="text-sm text-slate-400">
|
|
401→ {style === 'minimal' && 'Clean and simple design'}
|
|
402→ {style === 'modern' && 'Contemporary with subtle effects'}
|
|
403→ {style === 'classic' && 'Traditional and familiar layout'}
|
|
404→ </p>
|
|
405→ </button>
|
|
406→ ))}
|
|
407→ </div>
|
|
408→ </div>
|
|
409→ {/* Primary Color */}
|
|
410→ <div>
|
|
411→ <label className="block text-sm font-medium mb-2">Primary Color</label>
|
|
412→ <div className="flex gap-3">
|
|
413→ {['#0d9488', '#3b82f6', '#8b5cf6', '#ef4444', '#f59e0b'].map((color) => (
|
|
414→ <button
|
|
415→ key={color}
|
|
416→ onClick={() => setDesignPreferences({ ...designPreferences, primaryColor: color })}
|
|
417→ className={`w-10 h-10 rounded-lg border-2 transition-transform ${
|
|
418→ designPreferences.primaryColor === color
|
|
419→ ? 'border-white scale-110'
|
|
420→ : 'border-transparent hover:scale-105'
|
|
421→ }`}
|
|
422→ style={{ backgroundColor: color }}
|
|
423→ />
|
|
424→ ))}
|
|
425→ </div>
|
|
426→ </div>
|
|
427→ </div>
|
|
428→ </div>
|
|
429→ )}
|
|
430→
|
|
431→ {step === 5 && (
|
|
432→ <div>
|
|
433→ <h2 className="text-xl font-semibold mb-4">Review & Create</h2>
|
|
434→ <div className="space-y-4">
|
|
435→ {/* Project Basics */}
|
|
436→ <div className="bg-slate-700 rounded-lg p-4">
|
|
437→ <p className="text-sm text-slate-400">Project Name</p>
|
|
438→ <p className="font-semibold">{formData.name || 'Not set'}</p>
|
|
439→ </div>
|
|
440→ <div className="bg-slate-700 rounded-lg p-4">
|
|
441→ <p className="text-sm text-slate-400">Description</p>
|
|
442→ <p className="font-semibold">
|
|
443→ {formData.description || 'No description'}
|
|
444→ </p>
|
|
445→ </div>
|
|
446→ {/* Technology Stack */}
|
|
447→ <div className="bg-slate-700 rounded-lg p-4">
|
|
448→ <p className="text-sm text-slate-400">Technology Stack</p>
|
|
449→ <div className="mt-2 space-y-1">
|
|
450→ {techStack.frontend.length > 0 && (
|
|
451→ <p className="text-sm"><span className="text-slate-400">Frontend:</span> {techStack.frontend.join(', ')}</p>
|
|
452→ )}
|
|
453→ {techStack.backend.length > 0 && (
|
|
454→ <p className="text-sm"><span className="text-slate-400">Backend:</span> {techStack.backend.join(', ')}</p>
|
|
455→ )}
|
|
456→ {techStack.database.length > 0 && (
|
|
457→ <p className="text-sm"><span className="text-slate-400">Database:</span> {techStack.database.join(', ')}</p>
|
|
458→ )}
|
|
459→ {techStack.frontend.length === 0 && techStack.backend.length === 0 && techStack.database.length === 0 && (
|
|
460→ <p className="text-sm text-slate-500 italic">No technologies selected</p>
|
|
461→ )}
|
|
462→ </div>
|
|
463→ </div>
|
|
464→ {/* Features */}
|
|
465→ <div className="bg-slate-700 rounded-lg p-4">
|
|
466→ <p className="text-sm text-slate-400">Features</p>
|
|
467→ {features.length > 0 ? (
|
|
468→ <ul className="mt-2 list-disc list-inside text-sm">
|
|
469→ {features.map((feature, i) => (
|
|
470→ <li key={i}>{feature}</li>
|
|
471→ ))}
|
|
472→ </ul>
|
|
473→ ) : (
|
|
474→ <p className="mt-1 text-sm text-slate-500 italic">No features added</p>
|
|
475→ )}
|
|
476→ </div>
|
|
477→ {/* Design Preferences */}
|
|
478→ <div className="bg-slate-700 rounded-lg p-4">
|
|
479→ <p className="text-sm text-slate-400">Design Preferences</p>
|
|
480→ <div className="mt-2 space-y-1 text-sm">
|
|
481→ <p>
|
|
482→ <span className="text-slate-400">Design Mode:</span>{' '}
|
|
483→ <span className={`capitalize font-medium ${
|
|
484→ designPreferences.designMode === 'mvp' ? 'text-amber-400' : 'text-accent'
|
|
485→ }`}>
|
|
486→ {designPreferences.designMode === 'mvp' ? 'MVP (Functional Drafts)' : 'Finished (Polished)'}
|
|
487→ </span>
|
|
488→ </p>
|
|
489→ <p><span className="text-slate-400">Theme:</span> <span className="capitalize">{designPreferences.theme}</span></p>
|
|
490→ <p><span className="text-slate-400">Style:</span> <span className="capitalize">{designPreferences.style}</span></p>
|
|
491→ <p className="flex items-center gap-2">
|
|
492→ <span className="text-slate-400">Primary Color:</span>
|
|
493→ <span
|
|
494→ className="w-4 h-4 rounded inline-block"
|
|
495→ style={{ backgroundColor: designPreferences.primaryColor }}
|
|
496→ />
|
|
497→ </p>
|
|
498→ </div>
|
|
499→ </div>
|
|
500→ </div>
|
|
501→ </div>
|
|
502→ )}
|
|
503→ </div>
|
|
504→
|
|
505→ {/* Navigation */}
|
|
506→ <div className="flex items-center justify-between">
|
|
507→ <button
|
|
508→ onClick={step === 1 ? () => navigate('/') : handleBack}
|
|
509→ className="btn-secondary flex items-center gap-2"
|
|
510→ >
|
|
511→ <ArrowLeft size={18} />
|
|
512→ {step === 1 ? 'Cancel' : 'Back'}
|
|
513→ </button>
|
|
514→
|
|
515→ {step < totalSteps ? (
|
|
516→ <button
|
|
517→ onClick={handleNext}
|
|
518→ disabled={step === 1 && !formData.name}
|
|
519→ className="btn-accent flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
520→ >
|
|
521→ Next
|
|
522→ <ArrowRight size={18} />
|
|
523→ </button>
|
|
524→ ) : (
|
|
525→ <button
|
|
526→ onClick={handleSubmit}
|
|
527→ disabled={isLoading || !formData.name}
|
|
528→ className="btn-accent flex items-center gap-2 disabled:opacity-50 disabled:cursor-not-allowed"
|
|
529→ >
|
|
530→ {isLoading ? (
|
|
531→ <>
|
|
532→ <Loader2 size={18} className="animate-spin" />
|
|
533→ Creating...
|
|
534→ </>
|
|
535→ ) : (
|
|
536→ <>
|
|
537→ <Check size={18} />
|
|
538→ Create Project
|
|
539→ </>
|
|
540→ )}
|
|
541→ </button>
|
|
542→ )}
|
|
543→ </div>
|
|
544→ </div>
|
|
545→ )
|
|
546→}
|
|
547→
|
|
|
|
<system-reminder>
|
|
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
|
|
</system-reminder>
|