generated from muhtadeetaron/nextjs-template
initial commit
This commit is contained in:
24
examjam-admin/.gitignore
vendored
Normal file
24
examjam-admin/.gitignore
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
50
examjam-admin/README.md
Normal file
50
examjam-admin/README.md
Normal file
@ -0,0 +1,50 @@
|
||||
# React + TypeScript + Vite
|
||||
|
||||
This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.
|
||||
|
||||
Currently, two official plugins are available:
|
||||
|
||||
- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md) uses [Babel](https://babeljs.io/) for Fast Refresh
|
||||
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh
|
||||
|
||||
## Expanding the ESLint configuration
|
||||
|
||||
If you are developing a production application, we recommend updating the configuration to enable type aware lint rules:
|
||||
|
||||
- Configure the top-level `parserOptions` property like this:
|
||||
|
||||
```js
|
||||
export default tseslint.config({
|
||||
languageOptions: {
|
||||
// other options...
|
||||
parserOptions: {
|
||||
project: ['./tsconfig.node.json', './tsconfig.app.json'],
|
||||
tsconfigRootDir: import.meta.dirname,
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
- Replace `tseslint.configs.recommended` to `tseslint.configs.recommendedTypeChecked` or `tseslint.configs.strictTypeChecked`
|
||||
- Optionally add `...tseslint.configs.stylisticTypeChecked`
|
||||
- Install [eslint-plugin-react](https://github.com/jsx-eslint/eslint-plugin-react) and update the config:
|
||||
|
||||
```js
|
||||
// eslint.config.js
|
||||
import react from 'eslint-plugin-react'
|
||||
|
||||
export default tseslint.config({
|
||||
// Set the react version
|
||||
settings: { react: { version: '18.3' } },
|
||||
plugins: {
|
||||
// Add the react plugin
|
||||
react,
|
||||
},
|
||||
rules: {
|
||||
// other rules...
|
||||
// Enable its recommended rules
|
||||
...react.configs.recommended.rules,
|
||||
...react.configs['jsx-runtime'].rules,
|
||||
},
|
||||
})
|
||||
```
|
||||
BIN
examjam-admin/bun.lockb
Normal file
BIN
examjam-admin/bun.lockb
Normal file
Binary file not shown.
233
examjam-admin/components/ExamBuilder.tsx
Normal file
233
examjam-admin/components/ExamBuilder.tsx
Normal file
@ -0,0 +1,233 @@
|
||||
import React, { useState } from "react";
|
||||
|
||||
const ExamBuilder = () => {
|
||||
const [paper, setPaper] = useState({
|
||||
id: "",
|
||||
title: "",
|
||||
data: {
|
||||
metadata: {
|
||||
type: "single",
|
||||
marking: "",
|
||||
duration: 0,
|
||||
quantity: 0,
|
||||
},
|
||||
questions: [
|
||||
{
|
||||
id: 1,
|
||||
type: "single",
|
||||
options: {
|
||||
a: "",
|
||||
b: "",
|
||||
c: "",
|
||||
d: "",
|
||||
},
|
||||
question: "",
|
||||
solution: "",
|
||||
correctAnswer: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const handleChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
setPaper((prev) => ({
|
||||
...prev,
|
||||
[name]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
const handleMetadataChange = (e) => {
|
||||
const { name, value } = e.target;
|
||||
setPaper((prev) => ({
|
||||
...prev,
|
||||
data: {
|
||||
...prev.data,
|
||||
metadata: {
|
||||
...prev.data.metadata,
|
||||
[name]: value,
|
||||
},
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
const handleQuestionChange = (index, field, value) => {
|
||||
const updatedQuestions = paper.data.questions.map((q, i) =>
|
||||
i === index ? { ...q, [field]: value } : q
|
||||
);
|
||||
setPaper((prev) => ({
|
||||
...prev,
|
||||
data: { ...prev.data, questions: updatedQuestions },
|
||||
}));
|
||||
};
|
||||
|
||||
const handleOptionChange = (index, optionKey, value) => {
|
||||
const updatedQuestions = paper.data.questions.map((q, i) =>
|
||||
i === index ? { ...q, options: { ...q.options, [optionKey]: value } } : q
|
||||
);
|
||||
setPaper((prev) => ({
|
||||
...prev,
|
||||
data: { ...prev.data, questions: updatedQuestions },
|
||||
}));
|
||||
};
|
||||
|
||||
const addQuestion = () => {
|
||||
setPaper((prev) => ({
|
||||
...prev,
|
||||
data: {
|
||||
...prev.data,
|
||||
questions: [
|
||||
...prev.data.questions,
|
||||
{
|
||||
id: prev.data.questions.length + 1,
|
||||
type: "single",
|
||||
options: { a: "", b: "", c: "", d: "" },
|
||||
question: "",
|
||||
solution: "",
|
||||
correctAnswer: "",
|
||||
},
|
||||
],
|
||||
},
|
||||
}));
|
||||
};
|
||||
|
||||
const handleSubmit = (e) => {
|
||||
e.preventDefault();
|
||||
console.log("Exam Paper Submitted: ", paper);
|
||||
};
|
||||
|
||||
return (
|
||||
<section className="flex w-3/4 gap-10 h-fit">
|
||||
<section className="border-1 border-blue-100 rounded-2xl mt-5 py-10 px-10 max-h-[70vh] w-1/2">
|
||||
<h1 className="text-white text-3xl font-bold text-center mb-6">
|
||||
Exam Builder
|
||||
</h1>
|
||||
<form className="flex flex-col gap-4" onSubmit={handleSubmit}>
|
||||
<h2 className="text-white text-2xl">General Information</h2>
|
||||
<input
|
||||
type="text"
|
||||
name="title"
|
||||
value={paper.title}
|
||||
onChange={handleChange}
|
||||
placeholder="Exam Title"
|
||||
className="bg-white w-full rounded-full py-3 px-6"
|
||||
/>
|
||||
|
||||
<h2 className="text-white text-2xl">Metadata</h2>
|
||||
<select
|
||||
name="type"
|
||||
value={paper.data.metadata.type}
|
||||
onChange={handleMetadataChange}
|
||||
className="bg-white p-4 rounded-full"
|
||||
>
|
||||
<option value="single">Multiple Choice Questions (Single)</option>
|
||||
<option value="multiple">
|
||||
Multiple Choice Questions (Multiple)
|
||||
</option>
|
||||
</select>
|
||||
<input
|
||||
type="text"
|
||||
name="marking"
|
||||
value={paper.data.metadata.marking}
|
||||
onChange={handleMetadataChange}
|
||||
placeholder="Marking"
|
||||
className="bg-white w-full rounded-full py-3 px-6"
|
||||
/>
|
||||
<input
|
||||
type="number"
|
||||
name="duration"
|
||||
value={paper.data.metadata.duration}
|
||||
onChange={handleMetadataChange}
|
||||
placeholder="Duration"
|
||||
className="bg-white w-full rounded-full py-3 px-6"
|
||||
/>
|
||||
<input
|
||||
type="number"
|
||||
name="quantity"
|
||||
value={paper.data.metadata.quantity}
|
||||
onChange={handleMetadataChange}
|
||||
placeholder="How many questions?"
|
||||
className="bg-white w-full rounded-full py-3 px-6"
|
||||
/>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
className="bg-blue-500 text-white p-3 rounded-full mt-4"
|
||||
>
|
||||
Submit Exam
|
||||
</button>
|
||||
</form>
|
||||
</section>
|
||||
<section className="border-1 border-blue-100 rounded-2xl mt-5 py-10 px-10 w-1/2">
|
||||
<h1 className="text-white text-3xl font-bold text-center mb-6">
|
||||
Questions
|
||||
</h1>
|
||||
{paper.data.questions.map((question, index) => (
|
||||
<div key={index} className="rounded-lg">
|
||||
<h2 className="text-white text-xl mb-4">Question {index + 1}</h2>
|
||||
<input
|
||||
type="text"
|
||||
value={question.question}
|
||||
onChange={(e) =>
|
||||
handleQuestionChange(index, "question", e.target.value)
|
||||
}
|
||||
placeholder="Question"
|
||||
className="bg-white w-full rounded-full py-3 px-6 mb-2"
|
||||
/>
|
||||
<div className="">
|
||||
{Object.keys(question.options).map((key) => (
|
||||
<div key={key} className="flex items-center gap-2 mb-2">
|
||||
<h3 className="text-white uppercase py-3 px-5 rounded-full bg-blue-600">
|
||||
{key}
|
||||
</h3>
|
||||
<input
|
||||
type="text"
|
||||
value={question.options[key]}
|
||||
onChange={(e) =>
|
||||
handleOptionChange(index, key, e.target.value)
|
||||
}
|
||||
placeholder={`Option ${key.toUpperCase()}`}
|
||||
className="bg-white w-full rounded-full py-3 px-6"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
<input
|
||||
type="text"
|
||||
value={question.correctAnswer.toLowerCase()}
|
||||
onChange={(e) =>
|
||||
handleQuestionChange(index, "correctAnswer", e.target.value)
|
||||
}
|
||||
placeholder="Correct answer (a, b, c, d)"
|
||||
className="bg-white w-full rounded-full py-3 px-6 mt-2"
|
||||
/>
|
||||
<input
|
||||
type="text"
|
||||
value={question.solution}
|
||||
onChange={(e) =>
|
||||
handleQuestionChange(index, "solution", e.target.value)
|
||||
}
|
||||
placeholder="Solution"
|
||||
className="bg-white w-full rounded-full py-3 px-6 mt-2"
|
||||
/>
|
||||
</div>
|
||||
))}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
onClick={addQuestion}
|
||||
className="bg-green-500 text-white p-3 rounded-full mt-2"
|
||||
>
|
||||
Add Question
|
||||
</button>
|
||||
</section>
|
||||
</section>
|
||||
);
|
||||
};
|
||||
|
||||
export default ExamBuilder;
|
||||
|
||||
{
|
||||
/* */
|
||||
}
|
||||
28
examjam-admin/eslint.config.js
Normal file
28
examjam-admin/eslint.config.js
Normal file
@ -0,0 +1,28 @@
|
||||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import tseslint from 'typescript-eslint'
|
||||
|
||||
export default tseslint.config(
|
||||
{ ignores: ['dist'] },
|
||||
{
|
||||
extends: [js.configs.recommended, ...tseslint.configs.recommended],
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
},
|
||||
plugins: {
|
||||
'react-hooks': reactHooks,
|
||||
'react-refresh': reactRefresh,
|
||||
},
|
||||
rules: {
|
||||
...reactHooks.configs.recommended.rules,
|
||||
'react-refresh/only-export-components': [
|
||||
'warn',
|
||||
{ allowConstantExport: true },
|
||||
],
|
||||
},
|
||||
},
|
||||
)
|
||||
13
examjam-admin/index.html
Normal file
13
examjam-admin/index.html
Normal file
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>ExamJam | Admin</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
32
examjam-admin/package.json
Normal file
32
examjam-admin/package.json
Normal file
@ -0,0 +1,32 @@
|
||||
{
|
||||
"name": "examjam-admin",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@tailwindcss/vite": "^4.0.4",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router": "^7.1.5",
|
||||
"tailwindcss": "^4.0.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.15.0",
|
||||
"@types/react": "^18.3.12",
|
||||
"@types/react-dom": "^18.3.1",
|
||||
"@vitejs/plugin-react": "^4.3.4",
|
||||
"eslint": "^9.15.0",
|
||||
"eslint-plugin-react-hooks": "^5.0.0",
|
||||
"eslint-plugin-react-refresh": "^0.4.14",
|
||||
"globals": "^15.12.0",
|
||||
"typescript": "~5.6.2",
|
||||
"typescript-eslint": "^8.15.0",
|
||||
"vite": "^6.0.1"
|
||||
}
|
||||
}
|
||||
15
examjam-admin/src/App.tsx
Normal file
15
examjam-admin/src/App.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import ExamBuilder from "../components/ExamBuilder";
|
||||
|
||||
function App() {
|
||||
return (
|
||||
<main className="bg-slate-900 h-screen flex flex-col items-center">
|
||||
<header className="flex w-full justify-between px-80 py-4 border-1 border-b-slate-500 bg-slate-200">
|
||||
<h1 className="text-xl font-semibold">ExamJam</h1>
|
||||
<button className="text-xl">Logout</button>
|
||||
</header>
|
||||
<ExamBuilder />
|
||||
</main>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
1
examjam-admin/src/index.css
Normal file
1
examjam-admin/src/index.css
Normal file
@ -0,0 +1 @@
|
||||
@import "tailwindcss";
|
||||
15
examjam-admin/src/main.tsx
Normal file
15
examjam-admin/src/main.tsx
Normal file
@ -0,0 +1,15 @@
|
||||
import { StrictMode } from "react";
|
||||
import { createRoot } from "react-dom/client";
|
||||
import "./index.css";
|
||||
import App from "./App.tsx";
|
||||
import { BrowserRouter, Route, Routes } from "react-router";
|
||||
|
||||
createRoot(document.getElementById("root")!).render(
|
||||
<StrictMode>
|
||||
<BrowserRouter>
|
||||
<Routes>
|
||||
<Route path="/" element={<App />} />
|
||||
</Routes>
|
||||
</BrowserRouter>
|
||||
</StrictMode>
|
||||
);
|
||||
1
examjam-admin/src/vite-env.d.ts
vendored
Normal file
1
examjam-admin/src/vite-env.d.ts
vendored
Normal file
@ -0,0 +1 @@
|
||||
/// <reference types="vite/client" />
|
||||
26
examjam-admin/tsconfig.app.json
Normal file
26
examjam-admin/tsconfig.app.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2020",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
7
examjam-admin/tsconfig.json
Normal file
7
examjam-admin/tsconfig.json
Normal file
@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
24
examjam-admin/tsconfig.node.json
Normal file
24
examjam-admin/tsconfig.node.json
Normal file
@ -0,0 +1,24 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2022",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"isolatedModules": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
8
examjam-admin/vite.config.ts
Normal file
8
examjam-admin/vite.config.ts
Normal file
@ -0,0 +1,8 @@
|
||||
import { defineConfig } from "vite";
|
||||
import react from "@vitejs/plugin-react";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [react(), tailwindcss()],
|
||||
});
|
||||
Reference in New Issue
Block a user