initial commit

This commit is contained in:
shafin-r
2025-07-03 01:43:25 +06:00
commit 5dc53b896e
279 changed files with 28956 additions and 0 deletions

24
examjam-admin/.gitignore vendored Normal file
View 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
View 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

Binary file not shown.

View 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;
{
/* */
}

View 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
View 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>

View 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
View 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;

View File

@ -0,0 +1 @@
@import "tailwindcss";

View 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
View File

@ -0,0 +1 @@
/// <reference types="vite/client" />

View 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"]
}

View File

@ -0,0 +1,7 @@
{
"files": [],
"references": [
{ "path": "./tsconfig.app.json" },
{ "path": "./tsconfig.node.json" }
]
}

View 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"]
}

View 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()],
});