generated from muhtadeetaron/nextjs-template
fix(ui): fix ui icons and readability
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@ -16,6 +16,7 @@
|
|||||||
# next.js
|
# next.js
|
||||||
/.next/
|
/.next/
|
||||||
/out/
|
/out/
|
||||||
|
android
|
||||||
|
|
||||||
# production
|
# production
|
||||||
/build
|
/build
|
||||||
|
|||||||
@ -1,64 +1,64 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useState, useEffect } from "react";
|
import React from "react";
|
||||||
import BackgroundWrapper from "@/components/BackgroundWrapper";
|
import BackgroundWrapper from "@/components/BackgroundWrapper";
|
||||||
import { Bookmark, BookmarkCheck, ListFilter, MoveLeft } from "lucide-react";
|
import { ListFilter, MoveLeft } from "lucide-react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import DestructibleAlert from "@/components/DestructibleAlert";
|
import DestructibleAlert from "@/components/DestructibleAlert";
|
||||||
|
|
||||||
interface Question {
|
// interface Question {
|
||||||
id: number;
|
// id: number;
|
||||||
question: string;
|
// question: string;
|
||||||
options: Record<string, string>;
|
// options: Record<string, string>;
|
||||||
}
|
// }
|
||||||
|
|
||||||
interface QuestionItemProps {
|
// interface QuestionItemProps {
|
||||||
question: Question;
|
// question: Question;
|
||||||
}
|
// }
|
||||||
|
|
||||||
const QuestionItem = ({ question }: QuestionItemProps) => {
|
// const QuestionItem = ({ question }: QuestionItemProps) => {
|
||||||
const [bookmark, setBookmark] = useState(true);
|
// const [bookmark, setBookmark] = useState(true);
|
||||||
return (
|
// return (
|
||||||
<div className="border border-[#8abdff]/50 rounded-2xl p-4 flex flex-col gap-7">
|
// <div className="border border-[#8abdff]/50 rounded-2xl p-4 flex flex-col gap-7">
|
||||||
<h3 className="text-xl font-medium">
|
// <h3 className="text-xl font-medium">
|
||||||
{question.id + 1}. {question.question}
|
// {question.id + 1}. {question.question}
|
||||||
</h3>
|
// </h3>
|
||||||
<div className="flex justify-between items-center">
|
// <div className="flex justify-between items-center">
|
||||||
<div></div>
|
// <div></div>
|
||||||
<button onClick={() => setBookmark(!bookmark)}>
|
// <button onClick={() => setBookmark(!bookmark)}>
|
||||||
{bookmark ? (
|
// {bookmark ? (
|
||||||
<BookmarkCheck size={25} color="#113768" />
|
// <BookmarkCheck size={25} color="#113768" />
|
||||||
) : (
|
// ) : (
|
||||||
<Bookmark size={25} color="#113768" />
|
// <Bookmark size={25} color="#113768" />
|
||||||
)}
|
// )}
|
||||||
</button>
|
// </button>
|
||||||
</div>
|
// </div>
|
||||||
<div className="flex flex-col gap-4 items-start">
|
// <div className="flex flex-col gap-4 items-start">
|
||||||
{Object.entries(question.options).map(([key, value]) => {
|
// {Object.entries(question.options).map(([key, value]) => {
|
||||||
return (
|
// return (
|
||||||
<div key={key} className="flex items-center gap-3">
|
// <div key={key} className="flex items-center gap-3">
|
||||||
<span className="px-2 py-1 flex items-center rounded-full border font-medium text-sm">
|
// <span className="px-2 py-1 flex items-center rounded-full border font-medium text-sm">
|
||||||
{key.toUpperCase()}
|
// {key.toUpperCase()}
|
||||||
</span>
|
// </span>
|
||||||
<span className="option-description">{value}</span>
|
// <span className="option-description">{value}</span>
|
||||||
</div>
|
// </div>
|
||||||
);
|
// );
|
||||||
})}
|
// })}
|
||||||
</div>
|
// </div>
|
||||||
</div>
|
// </div>
|
||||||
);
|
// );
|
||||||
};
|
// };
|
||||||
|
|
||||||
const BookmarkPage = () => {
|
const BookmarkPage = () => {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [questions, setQuestions] = useState<Question[]>([]);
|
// const [questions, setQuestions] = useState<Question[]>([]);
|
||||||
|
|
||||||
useEffect(() => {
|
// useEffect(() => {
|
||||||
fetch("/data/bookmark.json")
|
// fetch("/data/bookmark.json")
|
||||||
.then((res) => res.json())
|
// .then((res) => res.json())
|
||||||
.then((data) => setQuestions(data))
|
// .then((data) => setQuestions(data))
|
||||||
.catch((err) => console.error("Error loading questions: ", err));
|
// .catch((err) => console.error("Error loading questions: ", err));
|
||||||
}, []);
|
// }, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<BackgroundWrapper>
|
<BackgroundWrapper>
|
||||||
|
|||||||
@ -6,7 +6,7 @@ import Header from "@/components/Header";
|
|||||||
import DestructibleAlert from "@/components/DestructibleAlert";
|
import DestructibleAlert from "@/components/DestructibleAlert";
|
||||||
import BackgroundWrapper from "@/components/BackgroundWrapper";
|
import BackgroundWrapper from "@/components/BackgroundWrapper";
|
||||||
import { API_URL, getToken } from "@/lib/auth";
|
import { API_URL, getToken } from "@/lib/auth";
|
||||||
import { Loader, RefreshCw, Star, StarHalf } from "lucide-react";
|
import { Loader, RefreshCw } from "lucide-react";
|
||||||
import { useAuthStore } from "@/stores/authStore";
|
import { useAuthStore } from "@/stores/authStore";
|
||||||
import { FaStar } from "react-icons/fa";
|
import { FaStar } from "react-icons/fa";
|
||||||
|
|
||||||
|
|||||||
@ -17,7 +17,7 @@ const tabs = [
|
|||||||
href: "/categories",
|
href: "/categories",
|
||||||
component: <HiViewGrid size={30} />,
|
component: <HiViewGrid size={30} />,
|
||||||
},
|
},
|
||||||
{ name: "Bookmark", href: "/bookmark", component: <FaBookmark size={25} /> },
|
{ name: "Bookmark", href: "/bookmark", component: <FaBookmark size={23} /> },
|
||||||
{ name: "Settings", href: "/settings", component: <HiCog6Tooth size={30} /> },
|
{ name: "Settings", href: "/settings", component: <HiCog6Tooth size={30} /> },
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@ -44,22 +44,24 @@ const SettingsPage = () => {
|
|||||||
<UserCircle2 size={30} color="#113768" />
|
<UserCircle2 size={30} color="#113768" />
|
||||||
</button>
|
</button>
|
||||||
</section>
|
</section>
|
||||||
<section className="flex flex-col gap-4">
|
<section className="flex items-center gap-4 bg-blue-100 border-1 border-blue-300/40 rounded-2xl py-5 px-4">
|
||||||
<div className="flex gap-4 items-center">
|
|
||||||
<Avatar className="bg-[#113768] w-20 h-20">
|
<Avatar className="bg-[#113768] w-20 h-20">
|
||||||
<AvatarFallback className="text-3xl text-white">
|
<AvatarFallback className="text-3xl text-white">
|
||||||
{user?.username
|
{user?.username
|
||||||
? user.username.charAt(0).toUpperCase()
|
? user.username.charAt(0).toUpperCase()
|
||||||
: ""}
|
: "User"}
|
||||||
</AvatarFallback>
|
</AvatarFallback>
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div className="flex flex-col items-start">
|
<div className="flex flex-col items-start">
|
||||||
<h1 className="font-semibold text-2xl">{user?.full_name}</h1>
|
<h1 className="font-semibold text-2xl flex gap-1 items-center">
|
||||||
|
{user?.full_name}
|
||||||
|
</h1>
|
||||||
<h3 className=" text-md">{user?.email}</h3>
|
<h3 className=" text-md">{user?.email}</h3>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
|
||||||
</section>
|
</section>
|
||||||
<section className="flex flex-col gap-8">
|
<section className="flex flex-col gap-8">
|
||||||
|
{!user?.is_verified && (
|
||||||
|
<>
|
||||||
<button
|
<button
|
||||||
onClick={() => router.push("/settings/verify")}
|
onClick={() => router.push("/settings/verify")}
|
||||||
className="flex justify-between"
|
className="flex justify-between"
|
||||||
@ -73,6 +75,8 @@ const SettingsPage = () => {
|
|||||||
<ChevronRight size={30} color="#113768" />
|
<ChevronRight size={30} color="#113768" />
|
||||||
</button>
|
</button>
|
||||||
<div className="h-[0.5px] border-[0.1px] w-full border-[#113768]/20"></div>
|
<div className="h-[0.5px] border-[0.1px] w-full border-[#113768]/20"></div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
<button
|
<button
|
||||||
onClick={() => router.push("/profile")}
|
onClick={() => router.push("/profile")}
|
||||||
className="flex justify-between"
|
className="flex justify-between"
|
||||||
|
|||||||
@ -9,7 +9,6 @@ import {
|
|||||||
InputOTPSlot,
|
InputOTPSlot,
|
||||||
} from "@/components/ui/input-otp";
|
} from "@/components/ui/input-otp";
|
||||||
import { API_URL } from "@/lib/auth";
|
import { API_URL } from "@/lib/auth";
|
||||||
import { useAuthStore } from "@/stores/authStore";
|
|
||||||
import Image from "next/image";
|
import Image from "next/image";
|
||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
|
|
||||||
@ -17,7 +16,6 @@ const VerificationScreen = () => {
|
|||||||
const [otp, setOtp] = useState("");
|
const [otp, setOtp] = useState("");
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState<string | null>(null);
|
const [error, setError] = useState<string | null>(null);
|
||||||
const { fetchUser } = useAuthStore();
|
|
||||||
|
|
||||||
const handleVerify = async () => {
|
const handleVerify = async () => {
|
||||||
if (otp.length < 6) return;
|
if (otp.length < 6) return;
|
||||||
@ -39,7 +37,6 @@ const VerificationScreen = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 🔥 Call zustand action to update auth state
|
// 🔥 Call zustand action to update auth state
|
||||||
await fetchUser();
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
setError(err.message);
|
setError(err.message);
|
||||||
} finally {
|
} finally {
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { usePathname, useRouter, useSearchParams } from "next/navigation";
|
import { useRouter, useSearchParams } from "next/navigation";
|
||||||
import Header from "@/components/Header";
|
import Header from "@/components/Header";
|
||||||
import QuestionItem from "@/components/QuestionItem";
|
import QuestionItem from "@/components/QuestionItem";
|
||||||
import BackgroundWrapper from "@/components/BackgroundWrapper";
|
import BackgroundWrapper from "@/components/BackgroundWrapper";
|
||||||
@ -44,6 +44,10 @@ export default function ExamPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
initExam();
|
initExam();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
stopTimer();
|
||||||
|
};
|
||||||
}, [
|
}, [
|
||||||
type,
|
type,
|
||||||
test_id,
|
test_id,
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import React, { useEffect } from "react";
|
import React, { useCallback, useEffect } from "react";
|
||||||
import { ArrowLeft } from "lucide-react";
|
import { ArrowLeft } from "lucide-react";
|
||||||
import { useExamStore } from "@/stores/examStore";
|
import { useExamStore } from "@/stores/examStore";
|
||||||
import QuestionItem from "@/components/QuestionItem";
|
import QuestionItem from "@/components/QuestionItem";
|
||||||
@ -12,10 +12,10 @@ export default function ResultsPage() {
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { result, clearResult, setStatus, status } = useExamStore();
|
const { result, clearResult, setStatus, status } = useExamStore();
|
||||||
|
|
||||||
function handleBackToHome() {
|
const handleBackToHome = useCallback(() => {
|
||||||
clearResult();
|
clearResult();
|
||||||
router.replace("/categories");
|
router.replace("/categories");
|
||||||
}
|
}, [clearResult, router]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const handlePopState = () => {
|
const handlePopState = () => {
|
||||||
@ -28,7 +28,7 @@ export default function ResultsPage() {
|
|||||||
return () => {
|
return () => {
|
||||||
window.removeEventListener("popstate", handlePopState);
|
window.removeEventListener("popstate", handlePopState);
|
||||||
};
|
};
|
||||||
}, [status, router, setStatus]);
|
}, [status, router, setStatus, handleBackToHome]);
|
||||||
|
|
||||||
if (!result) {
|
if (!result) {
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -1,10 +1,9 @@
|
|||||||
// capacitor.config.ts
|
import type { CapacitorConfig } from "@capacitor/cli";
|
||||||
import { CapacitorConfig } from "@capacitor/cli";
|
|
||||||
|
|
||||||
const config: CapacitorConfig = {
|
const config: CapacitorConfig = {
|
||||||
appId: "com.examjam.omukk",
|
appId: "com.examjam.omukk",
|
||||||
appName: "ExamJam",
|
appName: "ExamJam",
|
||||||
webDir: "out", // ✅ point to your Next.js static export
|
webDir: "out",
|
||||||
};
|
};
|
||||||
|
|
||||||
export default config;
|
export default config;
|
||||||
|
|||||||
@ -1,25 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import { useNavStore } from "@/stores/navStore";
|
|
||||||
import { useRouter, usePathname } from "next/navigation";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
|
|
||||||
export function ExamGuard({ children }: { children: React.ReactNode }) {
|
|
||||||
const { examSubmitted, resultsDone } = useNavStore();
|
|
||||||
const router = useRouter();
|
|
||||||
const pathname = usePathname();
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Prevent access to /exam after submission
|
|
||||||
if (pathname === "/exam/exam-screen" && examSubmitted) {
|
|
||||||
router.replace("/results");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Prevent access to /results after done
|
|
||||||
if (pathname === "/exam/results" && resultsDone) {
|
|
||||||
router.replace("/categories"); // or wherever you want them to go
|
|
||||||
}
|
|
||||||
}, [pathname, examSubmitted, resultsDone, router]);
|
|
||||||
|
|
||||||
return <>{children}</>;
|
|
||||||
}
|
|
||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import React from "react";
|
import React from "react";
|
||||||
import { useRouter } from "next/navigation";
|
import { useRouter } from "next/navigation";
|
||||||
import { BadgeCheck, ChevronLeft, Layers } from "lucide-react";
|
import { ChevronLeft, Layers, RefreshCcw } from "lucide-react";
|
||||||
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
||||||
import { useModal } from "@/context/ModalContext";
|
import { useModal } from "@/context/ModalContext";
|
||||||
import { useAuthStore } from "@/stores/authStore";
|
import { useAuthStore } from "@/stores/authStore";
|
||||||
@ -66,14 +66,19 @@ const Header = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{displayTabTitle && (
|
{displayTabTitle && (
|
||||||
|
<div className="flex justify-between items-center w-full">
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<button onClick={handleBackClick} className="bg-none border-none p-1">
|
<button
|
||||||
|
onClick={handleBackClick}
|
||||||
|
className="bg-none border-none p-1"
|
||||||
|
>
|
||||||
<ChevronLeft size={24} color="white" />
|
<ChevronLeft size={24} color="white" />
|
||||||
</button>
|
</button>
|
||||||
<span className="text-md font-bold text-white">
|
<span className="text-md font-bold text-white">
|
||||||
{displayTabTitle}
|
{displayTabTitle}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{displaySubject && (
|
{displaySubject && (
|
||||||
|
|||||||
@ -6,7 +6,8 @@
|
|||||||
"dev": "next dev --turbopack",
|
"dev": "next dev --turbopack",
|
||||||
"build": "next build ",
|
"build": "next build ",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"lint": "next lint"
|
"lint": "next lint",
|
||||||
|
"build:export": "npx next build && npm run export"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@capacitor/android": "^7.4.2",
|
"@capacitor/android": "^7.4.2",
|
||||||
|
|||||||
Reference in New Issue
Block a user