diff --git a/src/Callback.tsx b/src/Callback.tsx index a969494ddec25732d65085028ca677c06b1d383e..1ff448ad232b6257cfdac2ab266d9a447b9bdd8f 100644 --- a/src/Callback.tsx +++ b/src/Callback.tsx @@ -35,7 +35,7 @@ const Callback = () => { const response = await authIsOnboarded({client: client}); // Navigate based on onboarding status if (response.error) { - navigate("/onboarding"); + navigate("/onboardingQuiz"); } else if (response.data) { //console.log(response.data); if (response.data === true) { @@ -44,7 +44,7 @@ const Callback = () => { navigate("/error"); } } else if (response.data === false) { - navigate("/onboarding"); + navigate("/onboardingQuiz"); } diff --git a/src/main.tsx b/src/main.tsx index a77691eb29c5076e6edafb99b0629d6320e4b50d..2e41050c6bae04deea5838d4dfbdc1851d2785f9 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -28,6 +28,7 @@ import UncheckedPayments from "./pages/UncheckedPayments"; import KstEval from "./pages/KstEval"; import OwnList from "./pages/OwnList"; import ResponsibleList from "./pages/ResponsibleList"; +import OnboardingQuiz from "./pages/OnboardingQuiz"; const router = createBrowserRouter([ { @@ -80,6 +81,10 @@ const router = createBrowserRouter([ path: "onboarding", element: <Onboarding />, }, + { + path: "onboardingQuiz", + element: <OnboardingQuiz />, + }, { path: "CreditPayment/:idstring", element: <EditCreditPayment />, diff --git a/src/pages/Onboarding.tsx b/src/pages/Onboarding.tsx index b50e6ba16da1fada9fe280dd26df3327dfea5303..b37ee0eaf0c74c266df8a470a20c3ba18d2a36c8 100644 --- a/src/pages/Onboarding.tsx +++ b/src/pages/Onboarding.tsx @@ -81,14 +81,16 @@ export default function OnboardingPage() { if (response.error) { throw response.error; } else { - //maybe navigate somewhere + //navigate to / + window.location.href = "/"; + return; } }; return ( <Container> - here goes some text + Please enter your bank details. Any reimbursements will be sent to this account. Once you have entered your bank details, you will be able to submit your first reimbursement. <ObjectEditor fieldConfigs={fieldConfig} header="Onboarding" diff --git a/src/pages/OnboardingQuiz.tsx b/src/pages/OnboardingQuiz.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8200190917dce22ea2d77f9a923aef68468db9ac --- /dev/null +++ b/src/pages/OnboardingQuiz.tsx @@ -0,0 +1,255 @@ +import React, { useState } from "react"; +import { + Container, + Box, + Typography, + Button, + FormControl, + FormLabel, + RadioGroup, + FormControlLabel, + Radio, + Checkbox +} from "@mui/material"; + +export default function OnboardingCourse() { + interface PageContent { + id: number; + title: string; + description: string; + question?: string; + options?: string[]; // Multiple-choice options + correctAnswer?: string; // The correct option + isFinalPage?: boolean; // Whether this is the last page + } + + // Sample content with multiple-choice questions + const data: PageContent[] = [ + { + id: 1, + title: "Welcome to QTool! ", + description: "In the next few minutes, you will learn how to use the tool correctly. At the end of this onboarding, you will need to enter your IBAN and address, so please have them ready. You will also need to answer a small quiz on each page of this onboarding. Let's begin!", + question: "I will pay attention.", + options: ["Yes", "No", "Maybe"], + correctAnswer: "Yes", + }, + { + id: 2, + title: "What is Qtool?", + description: "QTool is used by AMIV to manage most financial processes. For you, it provides the ability to request reimbursements, enter any purchases made with an AMIV credit card, submit large bills that need to be paid by AMIV, and check the current status of the AMIV budget. If you are part of the board or a commission co-president, you will have detailed access to any transactions concerning the cost centers you are responsible for.", + question: "What is not a feature of QTool?", + options: ["Reimbursement Requests", "Rights management of AMIV IT services.", "AMIV Invoice Management", "Budget Overview"], + correctAnswer: "Rights management of AMIV IT services.", + }, + { + id: 3, + title: "Receipts are important!", + description: "The most common scenario for using QTool is when you spend money for AMIV (e.g., buying snacks for the biweekly EESTEC meeting). When spending money for AMIV, you ALWAYS need to ask for a receipt. If you don’t have the receipt, we cannot guarantee reimbursement. The receipt is always necessary, whether you use your own money or an AMIV credit card.", + question: "Do I need a receipt when spending money for AMIV?", + options: ["Only if I need a reimbursement", "Only if I use an AMIV Credit card", "I always need a receipt when spending money for AMIV", "Never, receipts are optional"], + correctAnswer: "I always need a receipt when spending money for AMIV", + }, + { + id: 4, + title: "Reimbursement Requests", + description: "When you want to get a reimbursement, choose the appropriate icon in the sidebar of QTool. Enter a short, unique, but precise description of what the money was spent on and what it is. Choose the correct cost center. If you are part of a commission, select the commission; if you spent the money for an AMIV event, select the event; if you are part of the board, you should know which option to choose. If in doubt, please contact the responsible person. Select the most appropriate ledger. Enter the exact amount stated on the receipt. Upload the receipt, making sure it is clear and that the whole receipt is visible. If you are happy with your choice, submit the form.", + question: "Which of the following is the best description of a reimbursement request?", + options: ["Gimme Money, was for snacks", "Snacks", "Beer for EESTEC meeting on 20.02.2014.", "Beer for EESTEC meeting"], + correctAnswer: "Beer for EESTEC meeting on 20.02.2014.", + }, + { + id: 5, + title: "AMIV Credit Card", + description: "If you use an AMIV credit card, the receipt also needs to be entered into QTool. The procedure is very similar to the reimbursement process, but you should choose the credit card form. Please be aware that you are responsible for the credit card when you have it with you, and AMIV could charge you for any usage of the card where you cannot provide the receipt.", + question: "How many expenses does AMIV have approximately in one year?", + options: ["30'000,-", "100'000,-", "650'000,-", "1'030'000,-"], + correctAnswer: "650'000,-", + }, + { + id: 6, + title: "Submitting Requests", + description: "Once you submit a request, the AMIV treasury will check it for correctness and either accept or decline it. You will be notified by email, and in the summary of QTool, you can see the status of all your requests.", + question: "When can I discard the original receipt?", + options: ["After I submitted the request", "After I received the money", "Never", "After the request was accepted"], + correctAnswer: "After the request was accepted", + }, + { + id: 7, + title: "Warning!", + description: "Use QTool responsibly and try your best to do everything correctly. The AMIV treasury will be very grateful. In the end, we all do this work voluntarily. Finally, every request will be thoroughly checked by the AMIV treasury; any fraudulent behavior will have consequences.", + isFinalPage: true, + } + ]; + + // State + const [currentPage, setCurrentPage] = useState(1); + const [selectedOption, setSelectedOption] = useState(""); + const [answeredPages, setAnsweredPages] = useState<number[]>([]); + const [termsAccepted, setTermsAccepted] = useState(false); + + // Derived values + const totalPages = data.length; + const currentItem = data[currentPage - 1]; + + // Handle multiple-choice selection + const handleOptionChange = (event: React.ChangeEvent<HTMLInputElement>) => { + setSelectedOption(event.target.value); + }; + + // Validate the selected answer + const checkAnswer = () => { + if ( + selectedOption.trim().toLowerCase() === + (currentItem.correctAnswer?.toLowerCase() ?? "") + ) { + // Mark page as answered correctly if not already + if (!answeredPages.includes(currentItem.id)) { + setAnsweredPages((prev) => [...prev, currentItem.id]); + } + setSelectedOption(""); // Clear the selection + // Auto-advance if not on the last page + if (currentPage < totalPages) { + setCurrentPage((prev) => prev + 1); + } + } else { + alert("Incorrect answer. Please try again!"); + } + }; + + // Navigate to previous page + const goToPreviousPage = () => { + if (currentPage > 1) { + setCurrentPage((prev) => prev - 1); + setSelectedOption(""); + } + }; + + // Navigate to next page (only if page is answered or is the final page) + const goToNextPage = () => { + // If it's a quiz page, must be answered before allowing next + if (currentItem?.correctAnswer) { + // If current page is answered, or we are on the final page + if (answeredPages.includes(currentItem.id) && currentPage < totalPages) { + setCurrentPage((prev) => prev + 1); + setSelectedOption(""); + } + } else if (currentItem.isFinalPage && currentPage < totalPages) { + // Jump directly to T&C page if user hasn't gone there yet + setCurrentPage((prev) => prev + 1); + } + }; + + // Handle T&C acceptance on the final page + const handleAcceptTerms = () => { + // Usually you'd call an API or do something else before redirecting + // Then redirect once accepted: + window.location.href = "/Onboarding"; + }; + + return ( + <Container + sx={{ + display: "flex", + flexDirection: "column", + justifyContent: "center", + alignItems: "center", + padding: 2, + height: "100vh", + }} + > + {/* Title & Description */} + <Box sx={{ textAlign: "center", marginBottom: 2 }}> + <Typography variant="h4">{currentItem.title}</Typography> + <Typography variant="body1">{currentItem.description}</Typography> + </Box> + + {/* If it's the final page, show Terms & Conditions instead of quiz */} + {currentItem.isFinalPage ? ( + <Box> + {/* Example: Simple checkbox for T&C acceptance */} + <Box sx={{ display: "flex", alignItems: "center", marginBottom: 2 }}> + <Checkbox + checked={termsAccepted} + onChange={() => setTermsAccepted(!termsAccepted)} + /> + <Typography variant="body2"> + I am aware of my responsibilities and will use QTool responsibly. + </Typography> + </Box> + + <Button + variant="contained" + disabled={!termsAccepted} + onClick={handleAcceptTerms} + > + Submit & Continue to enter IBAN and address + </Button> + </Box> + ) : ( + <> + {/* Multiple Choice Question */} + {currentItem.options && currentItem.question && ( + <Box sx={{ marginBottom: 2 }}> + <FormControl component="fieldset"> + <FormLabel component="legend" sx={{ marginBottom: 1 }}> + {currentItem.question} + </FormLabel> + <RadioGroup + value={selectedOption} + onChange={handleOptionChange} + sx={{ marginLeft: 2 }} + > + {currentItem.options.map((option) => ( + <FormControlLabel + key={option} + value={option} + control={<Radio />} + label={option} + /> + ))} + </RadioGroup> + </FormControl> + </Box> + )} + + {/* Submit Button for Quiz Pages */} + {currentItem.correctAnswer && ( + <Box sx={{ textAlign: "center", marginBottom: 2 }}> + <Button + variant="contained" + color="primary" + onClick={checkAnswer} + sx={{ marginRight: 2 }} + > + Submit Answer + </Button> + </Box> + )} + + {/* Navigation Buttons */} + <Box sx={{ display: "flex", justifyContent: "center", gap: 1 }}> + <Button + variant="contained" + onClick={goToPreviousPage} + disabled={currentPage === 1} + > + Previous + </Button> + <Button + variant="contained" + onClick={goToNextPage} + disabled={ + // If it's a quiz page, disable Next if it's not answered yet + (currentItem.correctAnswer && + !answeredPages.includes(currentItem.id)) || + currentPage === totalPages + } + > + Next + </Button> + </Box> + </> + )} + </Container> + ); +} \ No newline at end of file diff --git a/src/pages/index.tsx b/src/pages/index.tsx index 733375b3fd845e871e657e4000f8da804fc91f1b..f08a0315e549811d86f7a2b5d7cb53acc267e06a 100644 --- a/src/pages/index.tsx +++ b/src/pages/index.tsx @@ -14,10 +14,12 @@ import { Paper, Grid, } from "@mui/material"; -import { combinedReadCombinedPayments, authGetBasicUserInfo } from "../client/services.gen"; +import { combinedReadCombinedPayments, authGetBasicUserInfo, combinedReadOwnCombinedPayments } from "../client/services.gen"; +import { CombinedCreditor } from "../client"; +import GenericEditableTable from "../components/GenericEditableTable"; export default function WelcomeToQTool() { - const [lastFiveEntries, setLastFiveEntries] = useState<any[]>([]); + const [lastFiveEntries, setLastFiveEntries] = useState<CombinedCreditor[]>([]); const [user, setUser] = useState<any | null>(null); const [isFetchingUser, setIsFetchingUser] = useState(true); @@ -33,24 +35,28 @@ export default function WelcomeToQTool() { } else { setUser(userResponse.data); // Fetch last 5 entries if user is authenticated - const paymentResponse = await combinedReadCombinedPayments({ + const paymentResponse = await combinedReadOwnCombinedPayments({ query: { search: "", sort: "id:desc", + limit: 5, }, }); if (paymentResponse.error) { console.error("Error fetching combined payments:", paymentResponse.error); } else { - setLastFiveEntries(paymentResponse.data.items.slice(0, 5)); - } - } + console.log("recieved data"); + console.log(paymentResponse.data.items); + setLastFiveEntries(paymentResponse.data.items); + console.log(lastFiveEntries); + }} } catch (error) { console.error("Error fetching data:", error); setUser(null); } finally { setIsFetchingUser(false); } + }; // Fetch data on mount and when user state changes @@ -184,14 +190,6 @@ export default function WelcomeToQTool() { </TableRow> </TableHead> <TableBody> - {lastFiveEntries.map((row, index) => ( - <TableRow key={row.id || index}> - <TableCell>{row.creditor?.name || "Unknown"}</TableCell> - <TableCell>{row.creditor?.amount || "N/A"}</TableCell> - <TableCell>{row.creditor?.kst || "N/A"}</TableCell> - <TableCell>{row.creditor?.q_check || "N/A"}</TableCell> - </TableRow> - ))} </TableBody> </Table> </TableContainer>