-
Notifications
You must be signed in to change notification settings - Fork 1
Expand LanguageAI Features and Integrate Hugging Face #10
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
base: main
Are you sure you want to change the base?
Changes from all commits
ddda11e
f6f0779
e27265c
5e127cc
6373fe9
14b163c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -34,3 +34,5 @@ yarn-error.log* | |
| # typescript | ||
| *.tsbuildinfo | ||
| next-env.d.ts | ||
|
|
||
| /src/generated/prisma | ||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -24,6 +24,8 @@ import { | |||||||||||||||||||||
| import { Input } from "@/components/ui/input"; | ||||||||||||||||||||||
| import { Label } from "@/components/ui/label"; | ||||||||||||||||||||||
| import Link from "next/link"; | ||||||||||||||||||||||
| import { useRouter, useParams } from "next/navigation"; | ||||||||||||||||||||||
| import { useNotification } from "@/src/contexts"; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| type LoginProps = { | ||||||||||||||||||||||
| headingText: string; | ||||||||||||||||||||||
|
|
@@ -58,6 +60,10 @@ export default function LoginForm({ | |||||||||||||||||||||
| validationMessages, | ||||||||||||||||||||||
| }: LoginProps) { | ||||||||||||||||||||||
| const [showPassword, setShowPassword] = useState(false); | ||||||||||||||||||||||
| const router = useRouter(); | ||||||||||||||||||||||
| const params = useParams(); | ||||||||||||||||||||||
| const locale = params.locale as string; | ||||||||||||||||||||||
| const { notify } = useNotification(); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const loginSchema = useMemo( | ||||||||||||||||||||||
| () => | ||||||||||||||||||||||
|
|
@@ -77,8 +83,27 @@ export default function LoginForm({ | |||||||||||||||||||||
| defaultValues: { email: "", password: "", remember: false }, | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| const onSubmit = (values: LoginSchema) => { | ||||||||||||||||||||||
| console.log(values); | ||||||||||||||||||||||
| const onSubmit = async (values: LoginSchema) => { | ||||||||||||||||||||||
| try { | ||||||||||||||||||||||
| const response = await fetch("/api/auth/login", { | ||||||||||||||||||||||
| method: "POST", | ||||||||||||||||||||||
| headers: { "Content-Type": "application/json" }, | ||||||||||||||||||||||
| body: JSON.stringify(values), | ||||||||||||||||||||||
| }); | ||||||||||||||||||||||
| const data = await response.json(); | ||||||||||||||||||||||
| if (response.ok) { | ||||||||||||||||||||||
| localStorage.setItem("user_id", data.user.id); | ||||||||||||||||||||||
| localStorage.setItem("user_email", data.user.email); | ||||||||||||||||||||||
| localStorage.setItem("user_name", data.user.fullName || "User"); | ||||||||||||||||||||||
|
Comment on lines
+95
to
+97
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Storing sensitive user information like
Suggested change
|
||||||||||||||||||||||
| notify("Login successful!", "success"); | ||||||||||||||||||||||
| router.push(`/${locale}/dashboard`); | ||||||||||||||||||||||
|
Comment on lines
+95
to
+99
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Storing sensitive user information like ID, email, and name in Instead of storing this data in
Suggested change
|
||||||||||||||||||||||
| } else { | ||||||||||||||||||||||
| notify(data.error || "Login failed", "error"); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } catch (error) { | ||||||||||||||||||||||
| console.error("Login Error:", error); | ||||||||||||||||||||||
| notify("An error occurred during login", "error"); | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| }; | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| return ( | ||||||||||||||||||||||
|
|
@@ -138,6 +163,7 @@ export default function LoginForm({ | |||||||||||||||||||||
| className="rounded-[10px] w-full py-6" | ||||||||||||||||||||||
| /> | ||||||||||||||||||||||
| <button | ||||||||||||||||||||||
| type="button" | ||||||||||||||||||||||
| className="absolute right-3 rtl:left-3 rtl:right-auto top-1/2 -translate-y-1/2 cursor-pointer" | ||||||||||||||||||||||
| onClick={() => setShowPassword(!showPassword)} | ||||||||||||||||||||||
| > | ||||||||||||||||||||||
|
|
@@ -175,7 +201,7 @@ export default function LoginForm({ | |||||||||||||||||||||
| )} | ||||||||||||||||||||||
| /> | ||||||||||||||||||||||
| <Link | ||||||||||||||||||||||
| href="/forgot-password" | ||||||||||||||||||||||
| href={`/${locale}/forgot-password`} | ||||||||||||||||||||||
| className="text-sm md:text-base text-primary hover:underline" | ||||||||||||||||||||||
| > | ||||||||||||||||||||||
| {forgotPasswordLink} | ||||||||||||||||||||||
|
|
@@ -193,7 +219,7 @@ export default function LoginForm({ | |||||||||||||||||||||
|
|
||||||||||||||||||||||
| <div className="flex flex-row items-center justify-center gap-2 text-base mt-8"> | ||||||||||||||||||||||
| <p>{registerText}</p> | ||||||||||||||||||||||
| <Link href="/register"> | ||||||||||||||||||||||
| <Link href={`/${locale}/register`}> | ||||||||||||||||||||||
| <span className="text-primary">{registerLink}</span> | ||||||||||||||||||||||
| </Link> | ||||||||||||||||||||||
| </div> | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import { LiveChat } from "@/src/components/Chat/LiveChat"; | ||
|
|
||
| export default function ChatPage() { | ||
| return ( | ||
| <div className="p-6"> | ||
| <LiveChat /> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -8,17 +8,52 @@ import { useState, useEffect } from "react"; | |||||||||||||
| import { FaUpload, FaPlus, FaLanguage } from "react-icons/fa"; | ||||||||||||||
| import { BsGraphUp } from "react-icons/bs"; | ||||||||||||||
| import Link from "next/link"; | ||||||||||||||
| import { useParams } from "next/navigation"; | ||||||||||||||
|
|
||||||||||||||
| interface Translation { | ||||||||||||||
| id: string; | ||||||||||||||
| inputText: string; | ||||||||||||||
| sourceLanguage: string; | ||||||||||||||
| targetLanguage: string; | ||||||||||||||
| createdAt: string; | ||||||||||||||
| } | ||||||||||||||
|
|
||||||||||||||
| export default function Dashboard() { | ||||||||||||||
| const t = useTranslations("Dashboard"); | ||||||||||||||
| const params = useParams(); | ||||||||||||||
| const locale = params.locale as string; | ||||||||||||||
| const [userName, setUserName] = useState("User"); | ||||||||||||||
| const [stats, setStats] = useState({ | ||||||||||||||
| totalTranslations: 128, | ||||||||||||||
| wordsThisWeek: 4200, | ||||||||||||||
| documentsCount: 23, | ||||||||||||||
| mostUsedLanguage: "English → French", | ||||||||||||||
| totalTranslations: 0, | ||||||||||||||
| wordsThisWeek: 0, | ||||||||||||||
| documentsCount: 0, | ||||||||||||||
| mostUsedLanguage: "N/A", | ||||||||||||||
| }); | ||||||||||||||
| const [recentTranslations, setRecentTranslations] = useState<Translation[]>([]); | ||||||||||||||
|
|
||||||||||||||
| useEffect(() => { | ||||||||||||||
| const storedName = localStorage.getItem("user_name"); | ||||||||||||||
| if (storedName) setUserName(storedName); | ||||||||||||||
|
|
||||||||||||||
| useEffect(() => {}, []); | ||||||||||||||
| const fetchStats = async () => { | ||||||||||||||
| try { | ||||||||||||||
| const response = await fetch(`/api/user/stats`); | ||||||||||||||
| const data = await response.json(); | ||||||||||||||
| if (response.ok) { | ||||||||||||||
| setStats({ | ||||||||||||||
| totalTranslations: data.totalTranslations, | ||||||||||||||
| wordsThisWeek: data.wordsThisWeek, | ||||||||||||||
| documentsCount: data.documentsCount, | ||||||||||||||
| mostUsedLanguage: data.mostUsedLanguage, | ||||||||||||||
| }); | ||||||||||||||
| setRecentTranslations(data.recentTranslations); | ||||||||||||||
| } | ||||||||||||||
| } catch (error) { | ||||||||||||||
| console.error("Failed to fetch dashboard stats", error); | ||||||||||||||
| } | ||||||||||||||
| }; | ||||||||||||||
| fetchStats(); | ||||||||||||||
| }, []); | ||||||||||||||
|
|
||||||||||||||
| return ( | ||||||||||||||
| <div className="flex flex-col gap-8 p-6 md:p-10 w-full"> | ||||||||||||||
|
|
@@ -29,10 +64,10 @@ export default function Dashboard() { | |||||||||||||
| className="flex flex-col md:flex-row justify-between items-start md:items-center" | ||||||||||||||
| > | ||||||||||||||
| <h1 className="text-2xl md:text-3xl font-semibold text-gray-900"> | ||||||||||||||
| {t("Welcome", { name: "Olamide" })} | ||||||||||||||
| {t("Welcome", { name: userName })} | ||||||||||||||
| </h1> | ||||||||||||||
| <div className="flex gap-3 mt-4 md:mt-0"> | ||||||||||||||
| <Link href="/translation-history"> | ||||||||||||||
| <Link href={`/${locale}/dashboard`}> | ||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The "Start Translation" button currently links back to the dashboard page itself (
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The "Start Translation" button currently links to the dashboard page itself, which is where the user already is. This should likely navigate the user to the page where they can perform a new translation.
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The 'Start Translation' button currently links to the dashboard page itself, which is incorrect. It should navigate the user to the main translation page. Based on the file structure, the correct destination appears to be
Suggested change
|
||||||||||||||
| <Button className="flex items-center gap-2"> | ||||||||||||||
| <FaPlus /> {t("StartTranslation")} | ||||||||||||||
| </Button> | ||||||||||||||
|
|
@@ -114,35 +149,19 @@ export default function Dashboard() { | |||||||||||||
| <span>Date</span> | ||||||||||||||
| </div> | ||||||||||||||
|
|
||||||||||||||
| {[ | ||||||||||||||
| { | ||||||||||||||
| id: 1, | ||||||||||||||
| text: "Hello world", | ||||||||||||||
| lang: "English → French", | ||||||||||||||
| date: "2025-10-10", | ||||||||||||||
| }, | ||||||||||||||
| { | ||||||||||||||
| id: 2, | ||||||||||||||
| text: "Good morning", | ||||||||||||||
| lang: "English → Spanish", | ||||||||||||||
| date: "2025-10-09", | ||||||||||||||
| }, | ||||||||||||||
| { | ||||||||||||||
| id: 3, | ||||||||||||||
| text: "Translate this text", | ||||||||||||||
| lang: "English → German", | ||||||||||||||
| date: "2025-10-08", | ||||||||||||||
| }, | ||||||||||||||
| ].map((item) => ( | ||||||||||||||
| {recentTranslations.map((item) => ( | ||||||||||||||
| <div | ||||||||||||||
| key={item.id} | ||||||||||||||
| className="p-4 flex justify-between text-sm border-b last:border-none hover:bg-gray-50 transition" | ||||||||||||||
| > | ||||||||||||||
| <span className="truncate w-[40%]">{item.text}</span> | ||||||||||||||
| <span>{item.lang}</span> | ||||||||||||||
| <span className="text-gray-500">{item.date}</span> | ||||||||||||||
| <span className="truncate w-[40%]">{item.inputText}</span> | ||||||||||||||
| <span>{item.sourceLanguage} → {item.targetLanguage}</span> | ||||||||||||||
| <span className="text-gray-500">{new Date(item.createdAt).toLocaleDateString()}</span> | ||||||||||||||
| </div> | ||||||||||||||
| ))} | ||||||||||||||
| {recentTranslations.length === 0 && ( | ||||||||||||||
| <div className="p-8 text-center text-gray-400">No recent translations found.</div> | ||||||||||||||
| )} | ||||||||||||||
| </div> | ||||||||||||||
| </motion.div> | ||||||||||||||
| </div> | ||||||||||||||
|
|
||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| import { ImageTranslation } from "@/src/components/Translation/ImageTranslation"; | ||
|
|
||
| export default function ImageTranslationPage() { | ||
| return ( | ||
| <div className="p-6"> | ||
| <ImageTranslation /> | ||
| </div> | ||
| ); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import { Flashcards } from "@/src/components/Learning/Flashcards"; | ||
| import { DailyLearning } from "@/src/components/Learning/DailyLearning"; | ||
|
|
||
| export default function LearningPage() { | ||
| return ( | ||
| <div className="p-6 space-y-12"> | ||
| <Flashcards /> | ||
| <DailyLearning /> | ||
| </div> | ||
| ); | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Storing user details like ID, email, and name in
localStorageis a security risk. This data is accessible to any JavaScript running on the page, making it vulnerable to Cross-Site Scripting (XSS) attacks. Since you are already using a secure,httpOnlysession cookie, the frontend should rely on that session to fetch user data from a protected API endpoint when needed, rather than storing it inlocalStorageupon login.