Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Q/A Session Interface #25

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
f66b8e9
feat: created qna session page with temperory id
shristigupta12 Oct 17, 2023
82dccfb
feat: created question interface and mock questions data
shristigupta12 Oct 17, 2023
e41cc88
feat: created qna-question-card
shristigupta12 Oct 17, 2023
61a84d1
feat: rendered question-list
shristigupta12 Oct 17, 2023
ec2bfb5
fix: Link tab removed
shristigupta12 Oct 17, 2023
738ef59
styles: qna-question-sections
shristigupta12 Oct 22, 2023
8a90d09
feat: included text-area
shristigupta12 Oct 22, 2023
39a2067
styles: text-area fixes
shristigupta12 Oct 22, 2023
8d14fe5
feat: UI completed
shristigupta12 Oct 22, 2023
c3696c2
feat: remaining-time functionality added
shristigupta12 Oct 22, 2023
6e7ab87
feat: added functionality in questions
shristigupta12 Oct 23, 2023
c101f6a
fix: vercel deployment issue fixed
shristigupta12 Oct 23, 2023
02d99be
fix: question prerender fix
yashsehgal Oct 23, 2023
156b216
fix: minor style improvements & folder structure
yashsehgal Oct 23, 2023
76afcd6
fix: added random mock questions for better UI clarity
shristigupta12 Oct 23, 2023
f9149a1
fix: abstracted the code into components
shristigupta12 Oct 23, 2023
9dd0f58
fix: mock data fix
shristigupta12 Oct 23, 2023
c20bf71
feat: added TipTap instead of Text area
shristigupta12 Oct 23, 2023
289f70d
feat: dialog created with prevailing errros in the rendered ui
shristigupta12 Oct 23, 2023
84601a4
feat: dialog box ui completed
shristigupta12 Oct 24, 2023
982d362
add: answer updation feature
shristigupta12 Nov 25, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,5 @@ h4 {
.form-input-wrapper {
@apply grid grid-cols-1 gap-2 my-6;
}


1 change: 1 addition & 0 deletions app/qna/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { fetchTopics } from '@/middleware/qna/sessions';
import { getTopicDepthLevels } from '@/common';

const TopicBasedQNA: React.FunctionComponent = () => {

const [recentQNASessions, setRecentQNASessions] = useState<
Array<QNASessionCardInterface>
>(fetchRecentQNASessions());
Expand Down
35 changes: 35 additions & 0 deletions app/qna/session/[id]/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
'use client'
import PageContent from "@/components/layouts/page-content"
import ViewContainer from "@/components/layouts/view-container"
import { Button } from "@/components/ui/button"
import { QnaQuestionsList } from "@/components/sections/qna-questions-list"
import { fetchQnaQuestions } from "@/middleware/qna/qna-questions"
import { useState } from "react"
import { Timer } from "@/components/ui/qna-timer"
import { QnaQuestionAnswerSection } from "@/components/sections/qna-question-answer-section"
import { QnaEndSessionDialog } from "@/components/ui/qna-end-session-dialog"


const QNASession: React.FunctionComponent = () => {

const questionData = fetchQnaQuestions();
const [questionNum, setQuestionNum] = useState(0);

return (
<PageContent>
<ViewContainer className="flex justify-between gap-[120px]">
<QnaQuestionsList data={fetchQnaQuestions()} setQuestionNum={setQuestionNum} />
<div className="w-full flex flex-col gap-16 ">
<div className="remainingTime-endSessionAction-wrapper flex justify-between">
<Timer seconds={3600} />
{/* <Button variant="destructive" className="end-session-btn">End Session</Button> */}
<QnaEndSessionDialog variant="destructive" btnText="End Session" />
</div>
<QnaQuestionAnswerSection questionData={questionData} questionNum= {questionNum}/>
<QnaEndSessionDialog variant="default" btnText="Submit and End Session" classname="w-fit ml-auto mt-[-25px]" />
</div>
</ViewContainer>
</PageContent>
)
}
export default QNASession
2 changes: 1 addition & 1 deletion components/layouts/box-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ const BoxCard: React.FunctionComponent<
> = ({ className, children, ...props }) => (
<div
className={cn(
'relative w-[240px] h-[140px] rounded-2xl border-transparent shadow transition-all hover:shadow-md',
'relative w-[240px] h-[140px] rounded-2xl border-transparent shadow transition-all hover:shadow-md cursor:pointer',
'max-xl:w-[220px] max-xl:h-[120px]',
'max-lg:w-[200px] max-lg:h-[100px]',
'max-sm:rounded-xl',
Expand Down
46 changes: 46 additions & 0 deletions components/sections/qna-question-answer-section/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"use client"
import { Textarea } from "@/components/ui/textarea"
import { Button } from "@/components/ui/button"
import { Check } from 'lucide-react';
import Tiptap from "@/components/ui/tiptap";
import { fetchQnaQuestions } from "@/middleware/qna/qna-questions"
import { useEffect, useState } from "react";

function QnaQuestionAnswerSection(props:any){

const responseList = ()=>{
let response: {id: number, question: string, answer: string}[] = [];
fetchQnaQuestions().map((object,index)=>{
response.push({
id: index,
question: object.question,
answer: ""
})
})
return response;
}

const [responses, setResponses] = useState(responseList());

const handleTextAreaChange = (questionNum: number, value: string)=>{
let newResponses = [...responses];
newResponses[questionNum].answer = value;
setResponses(newResponses)
}

return(
<section>
<div className="flex flex-col gap-11">
<h2 className="question text-3xl font-semibold">{props.questionData[props.questionNum].question}</h2>
<div className="answer-wrapper ">
<p className="font-medium mb-1 text-sm" >Write your answer here</p>
<Textarea className="answer-box min-h-[300px] w-full px-4 py-2 placeholder:text-base text-base" placeholder="Good luck! You will do well" value={responses[props.questionNum].answer} onChange={(e)=>{handleTextAreaChange(props.questionNum, e.target.value)}}/>
{/* <Tiptap /> */}
</div>
</div>
<Button className="mark-complete-btn mt-3 flex gap-1"><Check /><span>Mark as completed</span></Button>
</section>
)
}

export {QnaQuestionAnswerSection};
25 changes: 25 additions & 0 deletions components/sections/qna-questions-list/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { QnaQuesCard } from "@/components/ui/qna-ques-card";
import { useState } from "react";

const QnaQuestionsList = (props:any) => {
return(
<section className="qna-question-list flex flex-col items-center">
<div className="qna-topic mb-7 text-xl font-bold">Frontend Engineering</div>
<div className="qna-questions flex flex-col gap-4 overflow-y-scroll max-h-[575px] hide-scrollbar h-[90svh]">
{props.data?.map((question:any) => {
return (
<QnaQuesCard
key={question.index}
question={question.question}
score = {question.score}
questionId={question.questionId}
setQuestionNum = {props.setQuestionNum}
/>
);
})}
</div>
</section>
)
}

export {QnaQuestionsList};
36 changes: 36 additions & 0 deletions components/ui/qna-end-session-dialog.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
'use client'
import React from "react";
import { Dialog, DialogTrigger, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "./dialog";
import { Button } from "./button";

interface QnaEndSessionDialogProps {
variant: string; // You can use a more specific type if necessary
btnText: string;
classname?: string;
}

const QnaEndSessionDialog: React.FunctionComponent<QnaEndSessionDialogProps> = (props) => {
return (
<>
<Dialog>
<DialogTrigger asChild>
<Button variant={props.btnText==="End Session"? "destructive": "default"} className={props.classname}>{props.btnText}</Button>
</DialogTrigger >
<DialogContent>
<DialogHeader className="p-2">
<DialogTitle className="mb-1">Are you sure about {props.btnText==="End Session"?"ending": "submitting"}?</DialogTitle>
<DialogDescription >
<p className="text-neutral-400 text-base mb-4">This action will end this test and will be considered as your final submission to this Q/A Session!</p>
<div className="flex justify-end gap-2 ">
<Button variant="outline" className="text-base text-stale-900 font-base">Ah, let me try more</Button>
<Button className="text-base font-base">Sure, Enought now</Button>
</div>
</DialogDescription>
</DialogHeader>
</DialogContent>
</Dialog>
</>
);
}

export { QnaEndSessionDialog };
17 changes: 17 additions & 0 deletions components/ui/qna-ques-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import Link from "next/link";
import { BoxCard } from "../layouts/box-card";
import { cn } from "@/lib/utils";

const QnaQuesCard: React.FunctionComponent<QnaQueCardInterface> = ({question, questionId, score, setQuestionNum})=>{
return(
<BoxCard className={cn('bg-neutral-200 hover:brightness-90 flex flex-col justify-between p-4 min-h-[140px] cursor-pointer')} onClick={() => setQuestionNum(questionId)}>
<div className="flex gap-1 align-items-center font-semibold text-lg truncate " >
<span>{questionId+1}.</span>
<p>{question}</p>
</div>
<span className="text-neutral-400">+{score} score</span>
</BoxCard>
)
}

export {QnaQuesCard};
51 changes: 51 additions & 0 deletions components/ui/qna-timer.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@

import { useEffect, useRef, useState } from "react";
import { Progress } from "@/components/ui/progress";

const formatTime = (time: number) => {
let minutes = Math.floor(time / 60);
let seconds = time % 60;

const formattedMinutes = minutes < 10 ? `0${minutes}` : `${minutes}`;
const formattedSeconds = seconds < 10 ? `0${seconds}` : `${seconds}`;

return `${formattedMinutes}:${formattedSeconds}`;
}

function Timer({ seconds }: any) {
const totalTime = seconds;
const [progressVal, setProgressVal] = useState(0);
const [timer, setTimer] = useState(seconds);
const timerId: any = useRef();

useEffect(() => {
timerId.current = setInterval(() => {
setTimer((prev: number) => prev - 1);
}, 1000);
return () => clearInterval(timerId.current);
}, []);

useEffect(() => {
const percent = ((totalTime - timer) / seconds) * 100; // Calculate progress relative to 'seconds'
setProgressVal(percent);
if (timer <= 0) {
clearInterval(timerId.current);
alert("Time up!");
}
}, [timer, seconds]);

return (
<div className="remainingTime-progress-wrapper w-3/4 flex flex-col gap-4">
<div className="remaining-time-wrapper">
<div className="remaining-time flex justify-between">
<h3 className="font-semibold text-lg">Remaining Time</h3>
<div className="text-neutral-400 font-medium">{formatTime(timer)}</div>
</div>
</div>
<Progress value={progressVal} />
</div>
);
}

export { Timer };

2 changes: 1 addition & 1 deletion components/ui/recent-session.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ const RecentSession: React.FunctionComponent<QNASessionCardInterface> = ({
<Link
href="#"
className="w-fit active:outline-none active:transition-none focus:outline-none h-fit rounded-2xl max-sm:rounded-xl hover:scale-95 active:scale-90 transition-all">
<BoxCard className={cn('bg-neutral-100 hover:brightness-90')}>
<BoxCard className={cn('bg-neutral-700 hover:brightness-90')}>
<MessageSquare
className={cn('absolute icon-wrapper top-4 right-4 text-black')}
/>
Expand Down
24 changes: 24 additions & 0 deletions components/ui/textarea.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import * as React from "react"

import { cn } from "@/lib/utils"

export interface TextareaProps
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}

const Textarea = React.forwardRef<HTMLTextAreaElement, TextareaProps>(
({ className, ...props }, ref) => {
return (
<textarea
className={cn(
"flex min-h-[300px] w-full rounded-md border border-neutral-200 hover:border-neutral-400 bg-white px-3 py-2 text-sm ring-offset-white placeholder:text-neutral-400 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-neutral-300 disabled:cursor-not-allowed disabled:opacity-50 dark:border-neutral-800 dark:bg-neutral-950 dark:ring-offset-neutral-950 dark:placeholder:text-neutral-400 dark:focus-visible:ring-neutral-300 text-neutral-700",
className
)}
ref={ref}
{...props}
/>
)
}
)
Textarea.displayName = "Textarea"

export { Textarea }
20 changes: 20 additions & 0 deletions components/ui/tiptap.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
'use client'

import { useEditor, EditorContent } from '@tiptap/react'
import StarterKit from '@tiptap/starter-kit'
import React from 'react'

const Tiptap = () => {
const editor = useEditor({
extensions: [
StarterKit
],
content: '<p>Hello World!🌎️</p>'
})

return (
<EditorContent editor={editor} />
)
}

export default Tiptap
16 changes: 16 additions & 0 deletions middleware/qna/qna-questions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Environment } from '@/common/environment-variables';
import { QnaMockQuestions } from '@/mocks/qna-questions-mock';

function fetchQnaQuestions(){
switch (Environment.ENVIRONMENT_TYPE){
case "development":
return QnaMockQuestions;
case "production":
//TODO Send topic-name and fetch questions-list with marks for each question via openAI
return [];
default:
return [];
}
}

export {fetchQnaQuestions};
67 changes: 67 additions & 0 deletions mocks/qna-questions-mock.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@


const QnaMockQuestions: Array<QnaQueCardInterface> = [
{
question: "Explain the difference between <div> and <span> elements.",
score: 10,
questionId: 0,
setQuestionNum: {}
},
{
question: "How do you center an element both horizontally and vertically using CSS?",
score: 10,
questionId: 1,
setQuestionNum: {}
},
{
question: "What is the DOM (Document Object Model) in JavaScript?",
score: 10,
questionId: 2,
setQuestionNum: {}
},
{
question: "How do you create a responsive webpage, and what are media queries?",
score: 10,
questionId: 3,
setQuestionNum: {}
},
{
question: "What is virtual DOM, and how does it work in React?",
score: 10,
questionId: 4,
setQuestionNum: {}
},
{
question: "How do you use Git for version control, and what are some common Git commands?",
score: 10,
questionId: 5,
setQuestionNum: {}
},
{
question: "Explain lazy loading and its benefits for web performance.",
score: 10,
questionId: 6,
setQuestionNum: {}
},
{
question: "How do you ensure a website is compatible with various web browsers?",
score: 10,
questionId: 7,
setQuestionNum: {}
},
{
question: "What is the purpose of build tools like Webpack or Grunt?",
score: 10,
questionId: 8,
setQuestionNum: {}
},
{
question: "What is unit testing, and how can you perform it in JavaScript?",
score: 10,
questionId: 9,
setQuestionNum: {}
}
]


export {QnaMockQuestions};
Loading