Skip to content

Commit

Permalink
frontend draft
Browse files Browse the repository at this point in the history
  • Loading branch information
xrenvtomate committed Nov 24, 2024
1 parent 99b8329 commit faf40b7
Show file tree
Hide file tree
Showing 20 changed files with 4,834 additions and 614 deletions.
4,035 changes: 4,035 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"lucide-react": "^0.446.0",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.28.0",
"tailwind-merge": "^2.5.2",
"tailwindcss-animate": "^1.0.7"
},
Expand Down
10 changes: 4 additions & 6 deletions src/App.css
Original file line number Diff line number Diff line change
@@ -1,9 +1,3 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}

.logo {
height: 6em;
Expand All @@ -18,6 +12,10 @@
filter: drop-shadow(0 0 2em #61dafbaa);
}

input:focus {
outline: none;
}

@keyframes logo-spin {
from {
transform: rotate(0deg);
Expand Down
44 changes: 17 additions & 27 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,34 +1,24 @@
import { useState } from 'react'
import reactLogo from './assets/react.svg'
import viteLogo from '/vite.svg'
import './App.css'
import "./App.css"
import UploadSection from "./components/UploadSection"
import QuizDisplay from "./components/QuizDisplay"
import Sidebar from "./components/Sidebar"
import MobileHeader from "./components/MobileHeader"
import { BrowserRouter as Router, Route, Routes } from "react-router-dom"

function App() {
const [count, setCount] = useState(0)

return (
<>
<div>
<a href="https://vitejs.dev" target="_blank">
<img src={viteLogo} className="logo" alt="Vite logo" />
</a>
<a href="https://react.dev" target="_blank">
<img src={reactLogo} className="logo react" alt="React logo" />
</a>
</div>
<h1>Vite + React</h1>
<div className="card">
<button onClick={() => setCount((count) => count + 1)}>
count is {count}
</button>
<p>
Edit <code>src/App.tsx</code> and save to test HMR
</p>
<Router>
<MobileHeader />
<div className="flex h-screen bg-layoutBG md:bg-backgroundGray md:p-4 pb-0 md:pb-4 gap-2 pt-16 md:pt-2">
<Sidebar />
<main className="flex-1 bg-layoutBG md:border md:rounded-xl border-layoutBorder pt-10 mb:pt-0 md:p-4 flex flex-col items-center overflow-y-auto px-4 md:px-0 pb-8 md:pb-0">
<Routes>
<Route path="/" element={<UploadSection />} />
<Route path="/quiz/:quiz_id" element={<QuizDisplay />} />{" "}
</Routes>
</main>
</div>
<p className="read-the-docs">
Click on the Vite and React logos to learn more
</p>
</>
</Router>
)
}

Expand Down
3 changes: 3 additions & 0 deletions src/assets/arrow-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/burger-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/close-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
16 changes: 16 additions & 0 deletions src/assets/kvizik-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/newnote-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/assets/test-icon.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 4 additions & 0 deletions src/assets/upload-icon.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
81 changes: 81 additions & 0 deletions src/components/MobileHeader.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import kvizikLogo from "../assets/kvizik-logo.svg"
import burgerIcon from "../assets/burger-icon.svg"
import closeIcon from "../assets/close-icon.svg"
import { useState } from "react"
import { Link } from "react-router-dom"
import ShadedButton from "./ShadedButton"
import testIcon from "../assets/test-icon.png"
import newNoteIcon from "../assets/newnote-icon.png"

export default function MobileHeader() {
const [isOpen, setIsOpen] = useState(false)

const quizzes = [
{
title: "First quiz",
id: "1",
},
{
title: "Second quiz",
id: "1",
},
{
title: "Third quiz",
id: "1",
},
]

return (
<div className="md:hidden fixed z-50 top-0 h-16 left-0 right-0 flex items-center justify-between px-4 border-b border-layoutBorder bg-layoutBG">
<div className="flex items-center gap-2 text-lg">
<img src={kvizikLogo} alt="Logo" />
Kvizik
</div>
<img
src={isOpen ? closeIcon : burgerIcon}
alt="Menu Toggle"
onClick={() => setIsOpen(!isOpen)}
className="cursor-pointer"
/>
<div
className={`fixed inset-0 top-16 bg-white transition-opacity z-30 ${
isOpen ? "opacity-1" : "opacity-0 pointer-events-none"
}`}
></div>
<div
className={`flex flex-col items-end fixed z-40 top-16 right-0 h-full bg-white w-full max-w-sm shadow-lg transform transition-transform p-8 ${
isOpen ? "translate-x-0" : "translate-x-full"
}`}
>
<div className="">
<Link to="/" onClick={() => setIsOpen(false)}>
<ShadedButton className="flex items-center gap-2 bg-white w-48">
<img src={testIcon} alt="test icon" className="h-6" />
Создать тест
</ShadedButton>
</Link>
<Link to="/" onClick={() => setIsOpen(false)}>
<ShadedButton className="flex items-center gap-2 bg-white w-48">
<img src={newNoteIcon} alt="test icon" className="h-6" />
Создать заметки
</ShadedButton>
</Link>
</div>
<div className="mt-12 text-right">
<h3 className="font-bold mb-2 flex items-center gap-12">Мои квизы</h3>
<ul className="flex gap-2 flex-col">
{quizzes.map((quiz) => (
<Link
key={quiz.title}
to={`/quiz/${quiz.id}`}
onClick={() => setIsOpen(false)}
>
<li>{quiz.title}</li>
</Link>
))}
</ul>
</div>
</div>
</div>
)
}
105 changes: 105 additions & 0 deletions src/components/QuizDisplay.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { useState } from "react"
import ShadedButton from "./ShadedButton"
import { useParams } from "react-router-dom"
import QuizQuestion from "./QuizQuestion"

type SelectedOptions = {
[questionId: string]: string | null
}
type Result = {
correct_answers: number
total_questions: number
}

const QuizDisplay = () => {
const { quiz_id } = useParams()

const [answers, setAnswers] = useState<string[] | null>(null)
const [result, setResult] = useState<Result | null>(null)
const [selectedOptions, setSelectedOptions] = useState<SelectedOptions>({})

function getAnswers() {
setAnswers(["123-123-123", "234", "51", "222"])
setResult({
correct_answers: 2,
total_questions: 3,
})
}
function restartQuiz() {
setAnswers(null)
setSelectedOptions({})
setResult(null)
}

const quiz = {
title: "Название теста",
description:
"Описание Explore fascinating topics: tardigrade resilience, Chicago River ecology, Burmese python adaptations, olfactory system breathing, and the elusive Ivory-billed Woodpecker. Discover nature's wonders and scientific marvels.",
questions: [
{
id: "1",
questionText: "What unique protein do tardigrades produce?",
options: [
{ text: "Dsup", id: "123-123-123" },
{ text: "Hsp70", id: "234" },
{ text: "Rad51", id: "51" },
{ text: "p53", id: "222" },
],
},
{
id: "2",
questionText: "What unique protein do tardigrades produce?",
options: [
{ text: "Dsup", id: "123-123-123" },
{ text: "Hsp70", id: "234" },
{ text: "Rad51", id: "51" },
{ text: "p53", id: "222" },
],
},
],
}

return (
<div className="pt-4 w-full max-w-[840px]">
<h2 className="text-lg font-bold mb-4">
{result ? "Ваш результат:" : quiz.title}
</h2>
{result ? (
<>
<p className="mb-4 p-4 border rounded-xl">
Вы правильно ответили на {result.correct_answers} из{" "}
{result.total_questions} вопросов
</p>
<ShadedButton className="bg-blue md:px-6" onClick={restartQuiz}>
Пройти заново
</ShadedButton>
</>
) : (
<>
<p className="mb-4">{quiz.description}</p>
<ShadedButton className="bg-blue md:px-6">Поделиться</ShadedButton>
</>
)}

{quiz.questions.map((question, index) => (
<QuizQuestion
key={question.id}
question={question}
selectedOptionId={selectedOptions[question.id]}
setSelectedOptionId={(id) =>
setSelectedOptions((prev) => ({ ...prev, [question.id]: id }))
}
correctAnswerId={answers ? answers[index] : null}
/>
))}

{result == null && (
<ShadedButton className="bg-blue md:px-6" onClick={getAnswers}>
Проверить ответы
</ShadedButton>
)}
</div>
)
}

export default QuizDisplay
53 changes: 53 additions & 0 deletions src/components/QuizQuestion.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import { cn } from "@/lib/utils"

export type Question = {
questionText: string
options: { text: string; id: string }[]
}

export default function QuizQuestion({
question,
selectedOptionId,
setSelectedOptionId,
correctAnswerId,
}: {
question: Question
selectedOptionId: string | null
setSelectedOptionId: (id: string) => void
correctAnswerId: string | null
}) {
return (
<div className="border-[1.5px] border-black rounded-xl p-4 pb-6 mb-4 mt-8">
<h3 className="mb-2">Вопрос 1</h3>
<p>What unique protein do tardigrades produce?</p>
<ul className="flex flex-col gap-3 mt-2">
{question.options.map((option, index) => (
<div className="relative" key={option.id}>
<div
onClick={() =>
correctAnswerId == null && setSelectedOptionId(option.id)
}
className={cn(
"relative z-10 flex gap-4 border-[1.5px] bg-white transiotion-all duration-200 border-black rounded-xl p-4 cursor-pointer",
correctAnswerId == null &&
option.id == selectedOptionId &&
" bg-purple",
correctAnswerId == option.id && " bg-answerGreen",
correctAnswerId &&
correctAnswerId != option.id &&
option.id == selectedOptionId &&
" bg-answerRed"
)}
>
<span className="font-bold">{["A", "B", "C", "D"][index]}.</span>
<button>{option.text}</button>
</div>
{correctAnswerId == null && option.id == selectedOptionId && (
<div className="border-[1.5px] border-black bg-black rounded-xl absolute top-1 left-1 w-full h-full"></div>
)}
</div>
))}
</ul>
</div>
)
}
34 changes: 34 additions & 0 deletions src/components/ShadedButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { cn } from "@/lib/utils"

export default function ShadedButton({
className,
children,
...props
}: {
className?: string
children: React.ReactNode
} & React.ButtonHTMLAttributes<HTMLButtonElement>) {
return (
<div className="relative flex-1">
<button
className={cn(
"border border-black rounded-xl px-2 md:px-4 py-3 relative z-10 mb-2",
className
)}
{...props}
>
{children}
</button>
<button
className={cn(
"border border-black rounded-xl px-2 md:px-4 py-3 ",
className,
"bg-black absolute top-1 left-1"
)}
{...props}
>
{children}
</button>
</div>
)
}
Loading

0 comments on commit faf40b7

Please sign in to comment.