diff --git a/.vscode/settings.json b/.vscode/settings.json index d53ecaf..e012065 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,4 +1,4 @@ { "java.compile.nullAnalysis.mode": "automatic", - "java.configuration.updateBuildConfiguration": "automatic" + "java.configuration.updateBuildConfiguration": "interactive" } \ No newline at end of file diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index eaf7c18..e184757 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -2,6 +2,7 @@ import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom' import { AuthProvider, useAuth } from './context/AuthContext' import ChallengeListPage from './pages/ChallengeListPage' import ChallengePage from './pages/ChallengePage' +import CreateChallengePage from './pages/CreateChallengePage' import AboutPage from './pages/AboutPage' import LoginPage from './pages/LoginPage' import RegisterPage from './pages/RegisterPage' @@ -27,9 +28,13 @@ function AppRoutes() { export default function App() { return ( - - - + + } /> + } /> + } /> + } /> + } /> + ) } diff --git a/frontend/src/pages/ChallengeListPage.jsx b/frontend/src/pages/ChallengeListPage.jsx index 7ce2a2f..302913d 100644 --- a/frontend/src/pages/ChallengeListPage.jsx +++ b/frontend/src/pages/ChallengeListPage.jsx @@ -31,8 +31,29 @@ export default function ChallengeListPage() { return ( <> -
+
+

+ SchemaLab +

+
+ + + New Challenge + + About +
+

Practice schema matching and schema versioning challenges.

diff --git a/frontend/src/pages/CreateChallengePage.jsx b/frontend/src/pages/CreateChallengePage.jsx new file mode 100644 index 0000000..a0cdca4 --- /dev/null +++ b/frontend/src/pages/CreateChallengePage.jsx @@ -0,0 +1,293 @@ +import { useState } from 'react' +import { useNavigate, Link } from 'react-router-dom' +import api from '../api/axios' + +const inputStyle = { + width: '100%', + background: '#2d3748', + border: '1px solid #4a5568', + borderRadius: 6, + color: '#f7fafc', + padding: '8px 12px', + fontSize: 14, + boxSizing: 'border-box', +} + +const monoStyle = { ...inputStyle, fontFamily: 'monospace', fontSize: 13, minHeight: 120, resize: 'vertical' } + +const labelStyle = { display: 'block', color: '#a0aec0', fontSize: 13, marginBottom: 6 } + +const fieldStyle = { marginBottom: 20 } + +const sectionStyle = { + border: '1px solid #2d3748', + borderRadius: 8, + padding: '16px 16px 8px', + marginBottom: 12, + background: '#1a202c', +} + +const addBtnStyle = { + background: 'none', + border: '1px dashed #4a5568', + color: '#a0aec0', + borderRadius: 6, + padding: '6px 16px', + fontSize: 13, + cursor: 'pointer', + width: '100%', + marginBottom: 20, +} + +const removeBtnStyle = { + background: 'none', + border: 'none', + color: '#fc8181', + fontSize: 12, + cursor: 'pointer', + padding: '0 4px', +} + +function emptySchema() { + return { label: '', format: 'JSON_SCHEMA', content: '' } +} + +function emptyTestCase() { + return { description: '', inputJson: '', expectedJson: '', hidden: false } +} + +export default function CreateChallengePage() { + const navigate = useNavigate() + const [form, setForm] = useState({ + title: '', + description: '', + difficulty: 'EASY', + type: 'SCHEMA_MATCHING', + starterCode: '', + harnessCode: '', + }) + const [schemas, setSchemas] = useState([emptySchema()]) + const [testCases, setTestCases] = useState([emptyTestCase()]) + const [error, setError] = useState(null) + const [submitting, setSubmitting] = useState(false) + + function setField(field) { + return e => setForm(f => ({ ...f, [field]: e.target.value })) + } + + function updateSchema(i, field, value) { + setSchemas(prev => prev.map((s, idx) => idx === i ? { ...s, [field]: value } : s)) + } + + function updateTestCase(i, field, value) { + setTestCases(prev => prev.map((t, idx) => idx === i ? { ...t, [field]: value } : t)) + } + + function addSchema() { setSchemas(prev => [...prev, emptySchema()]) } + function removeSchema(i) { setSchemas(prev => prev.filter((_, idx) => idx !== i)) } + + function addTestCase() { setTestCases(prev => [...prev, emptyTestCase()]) } + function removeTestCase(i) { setTestCases(prev => prev.filter((_, idx) => idx !== i)) } + + async function handleSubmit(e) { + e.preventDefault() + if (!form.title.trim()) { setError('Title is required.'); return } + setError(null) + setSubmitting(true) + try { + const payload = { + ...form, + schemas: schemas.filter(s => s.label.trim() || s.content.trim()), + testCases: testCases.filter(t => t.inputJson.trim() || t.expectedJson.trim()), + } + const res = await api.post('/challenges', payload) + navigate(`/challenges/${res.data.id}`) + } catch { + setError('Failed to create challenge. Please try again.') + setSubmitting(false) + } + } + + return ( +
+
+

New Challenge

+ Cancel +
+

+ Create a new schema challenge for others to solve. +

+ + {error &&

{error}

} + +
+ {/* ── Basic fields ── */} +
+ + +
+ +
+ +