Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
58 changes: 38 additions & 20 deletions client/src/components/AddCourseModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,12 @@ const AddCourseModal: React.FC<AddCourseModalProps> = ({ onClose, refreshData })
};
}, []);

useEffect(() => {
if (activeTab === "text" && currentStep === 3) {
setCurrentStep(2);
}
}, [activeTab, currentStep]);

useEffect(() => {
let isCancelled = false;

Expand Down Expand Up @@ -190,6 +196,11 @@ const AddCourseModal: React.FC<AddCourseModalProps> = ({ onClose, refreshData })
return;
}

if (activeTab === "text") {
handleSubmit();
return;
}

setSubmitError("");
setCurrentStep(3);
};
Expand All @@ -205,8 +216,13 @@ const AddCourseModal: React.FC<AddCourseModalProps> = ({ onClose, refreshData })
return;
}

if (!goalValue.trim()) {
setSubmitError("Please add what you want to achieve from this roadmap.");
const userGoalPayload = activeTab === "text" ? textValue.trim() : goalValue.trim();
if (!userGoalPayload) {
setSubmitError(
activeTab === "text"
? "Please enter your topic description."
: "Please add what you want to achieve from this roadmap."
);
return;
}

Expand All @@ -227,7 +243,7 @@ const AddCourseModal: React.FC<AddCourseModalProps> = ({ onClose, refreshData })
}

payload.append("time_query", durationValue.trim());
payload.append("user_goal", goalValue.trim());
payload.append("user_goal", userGoalPayload);

setIsSubmitting(true);
setSubmitError("");
Expand Down Expand Up @@ -297,24 +313,26 @@ const AddCourseModal: React.FC<AddCourseModalProps> = ({ onClose, refreshData })
>
Duration
</button>
<button
onClick={() => {
if (hasSourceInput() && durationValue.trim()) {
setSubmitError("");
setCurrentStep(3);
}
}}
className={`pb-2 text-sm uppercase tracking-widest transition-all ${
currentStep === 3
? "text-white border-b border-white"
: "text-text-secondary border-b border-transparent hover:text-white"
}`}
>
Goal
</button>
{activeTab !== "text" && (
<button
onClick={() => {
if (hasSourceInput() && durationValue.trim()) {
setSubmitError("");
setCurrentStep(3);
}
}}
className={`pb-2 text-sm uppercase tracking-widest transition-all ${
currentStep === 3
? "text-white border-b border-white"
: "text-text-secondary border-b border-transparent hover:text-white"
}`}
>
Goal
</button>
)}
</div>
<span className="text-xs uppercase tracking-[0.3em] text-text-secondary">
Step {currentStep} of 3
Step {currentStep} of {activeTab === "text" ? 2 : 3}
</span>
</div>

Expand Down Expand Up @@ -559,7 +577,7 @@ const AddCourseModal: React.FC<AddCourseModalProps> = ({ onClose, refreshData })
>
{currentStep === 1 ? "Cancel" : "Back"}
</button>
{currentStep === 3 ? (
{currentStep === (activeTab === "text" ? 2 : 3) ? (
<button
onClick={handleSubmit}
disabled={isSubmitting}
Expand Down
77 changes: 60 additions & 17 deletions client/src/components/QuizModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,38 @@ type QuizModalProps = {

type QuizState = "settings" | "taking" | "results";

export const checkAnswer = (question: QuizQuestion, userAnswer: string): boolean => {
if (!userAnswer || !question || !question.answer) return false;

const normUser = userAnswer.trim().toUpperCase();
const normCorrect = question.answer.trim().toUpperCase();

if (normUser === normCorrect) return true;

// If the correct answer is the full option text, check if the user selected that option
const userIndex = normUser.charCodeAt(0) - 65;
const userOptionText = question.options[userIndex];
if (userOptionText && userOptionText.trim().toUpperCase() === question.answer.trim().toUpperCase()) {
return true;
}

// If the correct answer is in the format "A) Text" or "A. Text"
if (normCorrect.startsWith(normUser) && (normCorrect.length === 1 || normCorrect[1] === ')' || normCorrect[1] === '.')) {
return true;
}

// Find if correct answer matches any option text, and see if the user selected that option's letter
const correctOptionIdx = question.options.findIndex(
(opt) => opt.trim().toUpperCase() === question.answer.trim().toUpperCase()
);
if (correctOptionIdx !== -1) {
const correctLetter = String.fromCharCode(65 + correctOptionIdx);
if (normUser === correctLetter) return true;
}

return false;
};

const QuizModal: React.FC<QuizModalProps> = ({
isOpen,
onClose,
Expand All @@ -45,20 +77,16 @@ const QuizModal: React.FC<QuizModalProps> = ({
const [quizState, setQuizState] = useState<QuizState>("settings");
const [userAnswers, setUserAnswers] = useState<Record<number, string>>({});
const [isSavingQuiz, setIsSavingQuiz] = useState(false);
const [generationInitiated, setGenerationInitiated] = useState(false);
const [isWaitingForQuiz, setIsWaitingForQuiz] = useState(false);

useEffect(() => {
if (!isOpen) {
setQuizState("settings");
setUserAnswers({});
setGenerationInitiated(false);
setIsWaitingForQuiz(false);
return;
}

if (isLoading) {
setGenerationInitiated(true);
}

const tl = gsap.timeline({ defaults: { ease: "power3.out" } });
tl.fromTo(overlayRef.current, { opacity: 0 }, { opacity: 1, duration: 0.25 }).fromTo(
modalRef.current,
Expand All @@ -70,7 +98,7 @@ const QuizModal: React.FC<QuizModalProps> = ({
return () => {
tl.kill();
};
}, [isOpen, isLoading]);
}, [isOpen]);

useEffect(() => {
if (!isOpen) {
Expand All @@ -89,19 +117,26 @@ const QuizModal: React.FC<QuizModalProps> = ({
}, [isOpen]);

const handleGenerateAndStart = () => {
setGenerationInitiated(true);
setIsWaitingForQuiz(true);
onGenerateQuiz();
};

useEffect(() => {
if (generationInitiated && !isLoading) {
if (questions.length > 0 && quizState === "settings") {
if (!isOpen) {
setIsWaitingForQuiz(false);
return;
}

if (isLoading) {
setIsWaitingForQuiz(true);
} else if (isWaitingForQuiz && questions.length > 0) {
if (quizState === "settings") {
setQuizState("taking");
setUserAnswers({});
}
setGenerationInitiated(false);
setIsWaitingForQuiz(false);
}
}, [isLoading, questions, quizState, generationInitiated]);
}, [isOpen, isLoading, questions, isWaitingForQuiz, quizState]);

const handleAnswerSelect = (questionIndex: number, selectedOption: string) => {
setUserAnswers((prev) => ({
Expand All @@ -119,7 +154,10 @@ const QuizModal: React.FC<QuizModalProps> = ({
setIsSavingQuiz(true);
try {
const correctCount = Object.entries(userAnswers).filter(
([index, answer]) => questions[Number(index)]?.answer === answer,
([index, answer]) => {
const q = questions[Number(index)];
return q && checkAnswer(q, answer);
},
).length;

await submitQuiz({
Expand Down Expand Up @@ -148,7 +186,10 @@ const QuizModal: React.FC<QuizModalProps> = ({
const score = useMemo(() => {
if (questions.length === 0) return 0;
const correct = Object.entries(userAnswers).filter(
([index, answer]) => questions[Number(index)]?.answer === answer,
([index, answer]) => {
const q = questions[Number(index)];
return q && checkAnswer(q, answer);
},
).length;
return Math.round((correct / questions.length) * 100);
}, [userAnswers, questions]);
Expand Down Expand Up @@ -266,16 +307,18 @@ const QuizModal: React.FC<QuizModalProps> = ({
<p className="font-sans text-xs uppercase tracking-[0.3em] text-[#d8bf92]">Your Score</p>
<p className="mt-4 text-5xl font-serif text-white">{score}%</p>
<p className="mt-2 font-sans text-sm text-text-secondary">
{Object.entries(userAnswers).filter(([index, answer]) => questions[Number(index)]?.answer === answer)
.length}{" "}
{Object.entries(userAnswers).filter(([index, answer]) => {
const q = questions[Number(index)];
return q && checkAnswer(q, answer);
}).length}{" "}
out of {questions.length} correct
</p>
</div>

<div className="space-y-3">
{questions.map((item, index) => {
const userAnswer = userAnswers[index];
const isCorrect = userAnswer === item.answer;
const isCorrect = userAnswer ? checkAnswer(item, userAnswer) : false;
return (
<article
key={`result-${index}`}
Expand Down
16 changes: 5 additions & 11 deletions client/src/pages/Login.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import Navigation from "../components/Navigation";
import OAuthButton from "../components/OAuthButton";
import gsap from "gsap";
import { Link, useNavigate } from "react-router-dom";
import { login, startOAuthLogin } from "../apis/authApi";
import { useAuth } from "../context/AuthContext";

const Login: React.FC = () => {
const containerRef = useRef<HTMLDivElement>(null);
const formRef = useRef<HTMLFormElement>(null);
const titleRef = useRef<HTMLHeadingElement>(null);
const navigate = useNavigate();
const { login, startOAuthLogin } = useAuth();
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [errorMessage, setErrorMessage] = useState("");
Expand All @@ -28,24 +29,17 @@ const Login: React.FC = () => {
setIsSubmitting(true);

try {
const response = await login({
const user = await login({
email,
password,
});

const data = response.data as {
success?: boolean;
token?: string;
user?: unknown;
message?: string;
};

if (data?.token || data?.user || data?.success) {
if (user) {
navigate("/dashboard");
return;
}

setStatusMessage(data?.message ?? "Login request completed.");
setStatusMessage("Login request completed.");
} catch {
setErrorMessage("Login failed. Please try again.");
} finally {
Expand Down
16 changes: 5 additions & 11 deletions client/src/pages/Signup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import Navigation from "../components/Navigation";
import OAuthButton from "../components/OAuthButton";
import gsap from "gsap";
import { Link, useNavigate } from "react-router-dom";
import { register, startOAuthLogin } from "../apis/authApi";
import { useAuth } from "../context/AuthContext";

const Signup: React.FC = () => {
const containerRef = useRef<HTMLDivElement>(null);
const formRef = useRef<HTMLFormElement>(null);
const titleRef = useRef<HTMLHeadingElement>(null);
const navigate = useNavigate();
const { register, startOAuthLogin } = useAuth();
const [fullName, setFullName] = useState("");
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
Expand All @@ -32,26 +33,19 @@ const Signup: React.FC = () => {
const lastName = rest.join(" ");

try {
const response = await register({
const user = await register({
firstName: firstName ?? "",
lastName,
email,
password,
});

const data = response.data as {
success?: boolean;
token?: string;
user?: unknown;
message?: string;
};

if (data?.token || data?.user || data?.success) {
if (user) {
navigate("/dashboard");
return;
}

setStatusMessage(data?.message ?? "Signup request completed.");
setStatusMessage("Signup request completed.");
} catch {
setErrorMessage("Signup failed. Please try again.");
} finally {
Expand Down
8 changes: 7 additions & 1 deletion client/src/service/baseUrl.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
import axios from "axios";

const normalizedBaseUrl = (
import.meta.env.VITE_API_BASE_URL ?? "https://curriculumos.onrender.com/api"
import.meta.env.VITE_API_BASE_URL ??
(typeof window !== "undefined" &&
(window.location.hostname === "localhost" ||
window.location.hostname === "127.0.0.1" ||
window.location.hostname === "0.0.0.0")
? "/api"
: "https://curriculumos.onrender.com/api")
).replace(/\/$/, "");

export const apiBaseUrl = normalizedBaseUrl;
Expand Down
7 changes: 7 additions & 0 deletions client/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,12 @@ export default defineConfig({
server:{
host:'127.0.0.1',
port:5173,
proxy: {
'/api': {
target: 'http://localhost:8080',
changeOrigin: true,
secure: false,
}
}
}
})
3 changes: 2 additions & 1 deletion python/app/rag/loaders/youtube.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ def load_youtube_video(url: str) -> list[Document]:
if proxy_username and proxy_password:
proxy_config = WebshareProxyConfig(
proxy_username=proxy_username,
proxy_password=proxy_password
proxy_password=proxy_password,
filter_ip_locations=["jp"]
)
api = YouTubeTranscriptApi(proxy_config=proxy_config)
else:
Expand Down
Loading
Loading