Skip to content

Commit

Permalink
add: quiz add form
Browse files Browse the repository at this point in the history
  • Loading branch information
domysh committed Sep 22, 2024
1 parent 74c0cb6 commit 726845c
Show file tree
Hide file tree
Showing 18 changed files with 281 additions and 40 deletions.
2 changes: 1 addition & 1 deletion .astro/astro/content.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -307,5 +307,5 @@ declare module 'astro:content' {

type AnyEntryMap = ContentEntryMap & DataEntryMap;

export type ContentConfig = typeof import("./../../src/content/config.js");
export type ContentConfig = typeof import("../../src/content/config.js");
}
31 changes: 30 additions & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,8 @@
"react-icons": "^5.3.0",
"react-qrcode-logo": "^3.0.0",
"sass": "^1.77.8",
"tailwindcss": "^3.3.5"
"tailwindcss": "^3.3.5",
"zustand": "^5.0.0-rc.2"
},
"devDependencies": {
"daisyui": "^4.12.10",
Expand Down
4 changes: 2 additions & 2 deletions src/pages/app.astro
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { PageComponent } from "../react/PageComponent";

<html lang="en" class="scroll-smooth flex flex-col min-h-screen">
<PageHead />
<body class="flex flex-col flex-1" style="width: 100vw; height:100vh">
<body class="flex flex-col flex-1">

<main class="flex flex-col justify-center items-center" style="width: 100vw; height:100vh">
<main class="flex flex-col justify-center items-center" style="min-width: 100vw; min-height:100vh">
<PageComponent client:only="react" page="app" />
</main>

Expand Down
4 changes: 2 additions & 2 deletions src/pages/login.astro
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { PageComponent } from "../react/PageComponent";

<html lang="en" class="scroll-smooth flex flex-col min-h-screen multicolored-bg">
<PageHead />
<body class="flex flex-col flex-1" style="width: 100vw; height:100vh">
<body class="flex flex-col flex-1">

<main class="flex flex-col justify-center items-center" style="width: 100vw; height:100vh">
<main class="flex flex-col justify-center items-center" style="min-width: 100vw; min-height:100vh">
<PageComponent client:only="react" page="login" />
</main>

Expand Down
4 changes: 2 additions & 2 deletions src/pages/redirect/[url_encoded].astro
Original file line number Diff line number Diff line change
Expand Up @@ -57,13 +57,13 @@ const t = useTranslations(lang);
some magic sprinkled in to help you build great templates. -->
<html lang={lang} class="scroll-smooth flex flex-col min-h-screen multicolored-bg">
<PageHead />
<body class="flex flex-col flex-1" style="width: 100vw; height:100vh">
<body class="flex flex-col flex-1">
<script>
document.addEventListener('DOMContentLoaded', () => {
location.href = location.href.replace(/https?/, "googlechrome")
})
</script>
<main class="flex flex-col justify-center items-center" style="width: 100vw; height:100vh">
<main class="flex flex-col justify-center items-center" style="min-width: 100vw; min-height:100vh">
<div class="jumbo h-192 lg:h-220 p-10 text-center text-primary-content flex flex-col align-center z-20 justify-center" style="width: 100%; height: 100%;">
<img src="/assets/vectors/logo_big.svg" class="h-36 m-2 md:m-6" />
<p class="text-xl md:text-3xl font-semibold">
Expand Down
4 changes: 2 additions & 2 deletions src/pages/signup.astro
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ import { PageComponent } from "../react/PageComponent";

<html lang="en" class="scroll-smooth flex flex-col min-h-screen multicolored-bg">
<PageHead />
<body class="flex flex-col flex-1" style="width: 100vw; height:100vh">
<body class="flex flex-col flex-1">

<main class="flex flex-col justify-center items-center" style="width: 100vw; height:100vh">
<main class="flex flex-col justify-center items-center" style="min-width: 100vw; min-height:100vh">
<PageComponent client:only="react" page="signup" />
</main>

Expand Down
4 changes: 2 additions & 2 deletions src/pages/ticket.astro
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ const t = useTranslations(lang);
some magic sprinkled in to help you build great templates. -->
<html lang={lang} class="scroll-smooth flex flex-col min-h-screen multicolored-bg">
<PageHead />
<body class="flex flex-col flex-1" style="width: 100vw; height:100vh">
<body class="flex flex-col flex-1">

<main class="flex flex-col justify-center items-center" style="width: 100vw; height:100vh">
<main class="flex flex-col justify-center items-center" style="min-width: 100vw; min-height:100vh">
<script>
import { WebsiteConfig } from "../config"

Expand Down
4 changes: 2 additions & 2 deletions src/react/components/AppBar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export const AppBar = () => {


return <Navbar className='bg-base-100 w-full border-b-4 border-b-red mb-4'>
<div className='flex-1'>
<div className='flex-1 lg:ml-5 ml-3'>
<img
src="/assets/vectors/logo_full.svg"
alt="Devfest Logo"
Expand All @@ -15,7 +15,7 @@ export const AppBar = () => {
</div>
<Button
onClick={() => firebase.auth.signOut()}
className='bg-base-100 text-white hover:bg-red-pastel hover:text-base-100 border-none'
className='bg-base-100 text-white hover:bg-red-pastel hover:text-base-100 border-none lg:mr-5 mr-3'
>
Logout
</Button>
Expand Down
44 changes: 27 additions & 17 deletions src/react/pages/AppPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,32 +5,42 @@ import { useFirebaseUserInfo } from "../utils/query";
import { AppBar } from "../components/AppBar";
import { UserInfoPage } from "./app/UserInfoPage";
import { EmailVerificationPage } from "./app/EmailVerificationPage";
import { useAppRouter } from "../utils/store";
import { QuizList } from "./app/QuizList";
import { Container } from "@mantine/core";
import { QuizAdd } from "./app/QuizAdd";


export const AppPage = () => {

const { user, hasLoaded } = useFirebaseUserInfo()
useEffect(() => {
if (user == null && hasLoaded) {
location.href = "/login"
}
}, [user])

const { currentPage, navigate } = useAppRouter()
const emailVerified = firebase.auth.currentUser?.emailVerified ?? false

let subPage = <AppMain>Loading...</AppMain>;

//TODO: Implement Sub Router for App Page
if(user && emailVerified){
subPage = UserInfoPage(user);
}else if(user && !emailVerified){
subPage = EmailVerificationPage(user);
}
useEffect(() => {
if (hasLoaded){
if (user == null) {
location.href = "/login"
}
if (user && !emailVerified && currentPage !== "verify-email") {
navigate("verify-email")
}
}
}, [user, hasLoaded])

return <div className="flex flex-col h-full w-full justify-start">
return <div className="flex flex-col h-full w-full justify-start" style={{minHeight: "100vh"}} >
<AppBar></AppBar>
<div className="flex-1 text-center">

{subPage}
<AppMain>
<Container size="xl" mt="xl">
{
currentPage == "verify-email"? <EmailVerificationPage user={user!}></EmailVerificationPage> :
currentPage == "app" ? <QuizList /> :
currentPage == "add-quiz" ? <QuizAdd /> :
currentPage == "profile"? <UserInfoPage user={user!}></UserInfoPage> : "Loading..."
}
</Container>
</AppMain>
</div>
</div>
}
3 changes: 2 additions & 1 deletion src/react/pages/SignupPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,8 @@ export const SignupPage = ({ token }: { token: string }) => {
value={pageStatus == SignUpPageStatus.SignUpInProgress ? "Loading" : "Signup"}
className={`btn btn-primary btn-wide ${pageStatus == SignUpPageStatus.SignUpInProgress ? "opacity-40 btn-warning" : ""}`}
onClick={checkErrors}
disabled={pageStatus == SignUpPageStatus.SignUpInProgress} />
disabled={pageStatus == SignUpPageStatus.SignUpInProgress}
/>
</div>
</div>
</Card>
Expand Down
4 changes: 1 addition & 3 deletions src/react/pages/app/EmailVerificationPage.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
import { useEffect } from "react"
import { useFirebaseUserInfo } from "../../utils/query"
import { sendEmailVerification, type User } from "firebase/auth"
import { Button } from "react-daisyui"
import { showNotification } from "@mantine/notifications"
import { AppMain } from "../../AppMain"

export const EmailVerificationPage = (user: User) => {
export const EmailVerificationPage = ({ user }:{ user: User }) => {
return <AppMain>
<div className="h-full flex flex-col justify-center items-center">
<div className="max-w-[60vw]">
Expand Down
167 changes: 167 additions & 0 deletions src/react/pages/app/QuizAdd.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
import { Button, Input, Radio, Select } from "react-daisyui"
import { useAppRouter } from "../../utils/store"
import { useForm } from "@mantine/form";
import { useState } from "react";
import { showNotification } from "@mantine/notifications";
import { IoMdArrowRoundBack } from "react-icons/io";

export const QuizAdd = () => {

const { navigate } = useAppRouter()
const [submitting, setSubmitting] = useState(false)

const talks = [
{ label: "Talk 1", value: "talk1" },
{ label: "Talk 2", value: "talk2" },
{ label: "Talk 3", value: "talk3" },
{ label: "Talk 4", value: "talk4" },
{ label: "Talk 5", value: "talk5" }
]

const form = useForm({
initialValues: {
title: "",
type: "talk",
talk: "",
questions: [0,1,2,3].map((qNum) => ({
question: "",
options: [
{ text: "", isCorrect: false },
{ text: "", isCorrect: false },
{ text: "", isCorrect: false },
{ text: "", isCorrect: false }
]
}))
},
validate: {
title: (value) => value.length > 0 ? undefined : "Title is required",
talk: (value) => value.length > 0 ? undefined : "Talk is required",
type: (value) => value == "talk" || value == "sponsor"? undefined : "Invalid type",
questions: (value) => {
let error: undefined|string = undefined
value.forEach((q, i) => {
if (q.question.length == 0){
return error = `Question title of ${i+1} is required`
}
let correctSet = false
q.options.forEach((o, j) => {
if (o.text.length == 0) return error = `Option ${j+1} of question ${i+1} is required`
if (o.isCorrect){
if (!correctSet) correctSet = true
else return error = `Only one correct option is allowed for question ${i+1}`
}
})
if (error != null) return error
if (!correctSet) return error = `Correct option of question ${i+1} is required`
})
return error
}
},
initialErrors: {
title: "Title is required",
talk: "Talk is required",
type: "Invalid type",
questions: "Questions are required"
}
})

const checkErrors = () => {
if (!form.isValid()) {
showNotification({
title: "Form error",
message: Object.values(form.errors).join(", "),
color: "red"
})
}
}

return <div className="flex-col h-full">
<div className="flex align-middle justify-center">
<h1 className="text-5xl font-bold">Add Quiz</h1>
<div className="flex-1" />
<Button className="btn-circle" onClick={()=>navigate("app")} >
<IoMdArrowRoundBack size={32} />
</Button>
</div>
<form onSubmit={form.onSubmit((data)=>{
setSubmitting(true)
console.log(data)
setSubmitting(false)
})}>
<div className="flex flex-col mt-10 w-full">
<div className="flex items-center w-full">
<h1 className="text-4xl font-bold mr-4">Title: </h1>
<Input placeholder="Main points of the talk" className="input input-bordered w-full text-3xl font-bold flex-1 py-5" size="lg" {...form.getInputProps("title")} />
</div>
<div className="mt-5 lg:flex">
<div className="form-control w-full flex justify-center lg:mr-10">
<label className="label">
<span className="label-text text-white">Quiz type</span>
</label>
<Select {...form.getInputProps("type")} className="select w-full">
<option value='talk'>Talk Quiz</option>
<option value="sponsor">Sponsor Quiz</option>
</Select>
</div>
{form.values.type == "talk" && <div className="form-control w-full flex justify-center">
<label className="label">
<span className="label-text text-white">Associated talk</span>
</label>
<Select {...form.getInputProps("talk")} className="select w-full">
{talks.map(talk => <option value={talk.value}>{talk.label}</option>)}
</Select>
</div>}
</div>
<div className="flex flex-col w-full lg:flex-wrap justify-center items-center lg:flex-row">
{[0,1,2,3].map((qNum) => <div className="flex flex-col mt-10 w-full justify-center items-center px-5" style={{ flexBasis: "50%" }}>
<div className="flex items-center w-full justify-center">
<h1 className="text-5xl font-bold mr-8">{qNum+1}:</h1>
<div className="form-control w-full flex justify-center max-w-lg">
<label className="label">
<span className="label-text text-white">Question</span>
</label>
<Input
placeholder="Type here"
className="input input-bordered w-full"
name={`questions.${qNum}.question`}
key={form.key(`questions.${qNum}.question`)}
{...form.getInputProps(`questions.${qNum}.question`)}
/>
</div>
</div>
{[0,1,2,3].map(opt => <div
key={form.key(`questions.${qNum}.${opt}.isCorrect`)}
className="flex items-center mt-5 w-full justify-center"
>
<Radio
name={`questions.${qNum}.options.isCorrect`}
key={form.key(`questions.${qNum}.options.${opt}.isCorrect`)}
checked={form.values.questions[qNum].options[opt].isCorrect}
onChange={(e) => {
form.values.questions[qNum].options.forEach((o, i) => {
form.setFieldValue(`questions.${qNum}.options.${i}.isCorrect`, false)
})
form.setFieldValue(`questions.${qNum}.options.${opt}.isCorrect`, e.target.checked)
}}
/>
<Input
placeholder="Type here"
className="input input-bordered w-full max-w-xs ml-5"
name={`questions.${qNum}.options.${opt}.text`}
key={form.key(`questions.${qNum}.options.${opt}.text`)}
{...form.getInputProps(`questions.${qNum}.options.${opt}.text`)}
/>
</div>)}
</div>)}
</div>
</div>
<Input
type="submit"
value={submitting ? "Loading" : "Add Quiz"}
className={`btn btn-primary btn-wide ${submitting || !form.isValid() ? "opacity-40 btn-warning" : ""} my-20`}
onClick={checkErrors}
disabled={submitting}
/>
</form>
</div>
}
Loading

0 comments on commit 726845c

Please sign in to comment.