fix(ts): refactor codebase for capacitor setup

This commit is contained in:
shafin-r
2025-07-28 20:22:04 +06:00
parent e091a78bdb
commit 0bca09f8ef
31 changed files with 458 additions and 384 deletions

View File

@ -1,6 +1,10 @@
import React from "react";
import React, { ReactNode } from "react";
const BackgroundWrapper = ({ children }) => {
interface BackgroundWrapperProps {
children: ReactNode;
}
const BackgroundWrapper = ({ children }: BackgroundWrapperProps) => {
return (
<div
className="min-h-screen bg-cover bg-center bg-no-repeat relative"
@ -10,9 +14,7 @@ const BackgroundWrapper = ({ children }) => {
>
<div
className="min-h-screen"
style={{
backgroundColor: "rgba(0, 0, 0, 0)", // Optional overlay - adjust opacity as needed
}}
style={{ backgroundColor: "rgba(0, 0, 0, 0)" }}
>
{children}
</div>

View File

@ -1,12 +1,14 @@
import React from "react";
interface DestructibleAlertProps {
text: string;
extraStyles?: string;
}
const DestructibleAlert = ({
text,
extraStyles = "",
}: {
text: string;
extraStyles?: string;
}) => {
}: DestructibleAlertProps) => {
return (
<div
className={`border bg-red-200 border-blue-200 rounded-3xl py-6 ${extraStyles}`}

View File

@ -1,4 +1,11 @@
import React, { useState } from "react";
import React, { useState, InputHTMLAttributes } from "react";
interface FormFieldProps extends InputHTMLAttributes<HTMLInputElement> {
title: string;
placeholder?: string;
value: string;
handleChangeText: (value: string) => void;
}
const FormField = ({
title,
@ -6,68 +13,38 @@ const FormField = ({
value,
handleChangeText,
...props
}) => {
}: FormFieldProps) => {
const [showPassword, setShowPassword] = useState(false);
const isPasswordField = title.toLowerCase().includes("password");
const isPasswordField = title === "Password" || title === "Confirm Password";
const inputId = `input-${title.replace(/\s+/g, "-").toLowerCase()}`;
return (
<div className="w-full">
<label
className="block mb-2"
style={{
color: "#666666",
fontFamily: "Montserrat, sans-serif",
fontWeight: "500",
fontSize: 18,
marginBottom: 8,
letterSpacing: "-0.5px",
}}
htmlFor={inputId}
className="block mb-2 text-[#666666] text-[18px] font-medium font-montserrat tracking-[-0.5px]"
>
{title}
</label>
<div
className="h-16 px-4 bg-blue-200 rounded-3xl flex items-center justify-between"
style={{
height: 64,
paddingLeft: 16,
paddingRight: 16,
backgroundColor: "#D2DFF0",
borderRadius: 20,
}}
>
<div className="h-16 px-4 bg-[#D2DFF0] rounded-3xl flex items-center justify-between">
<input
id={inputId}
type={isPasswordField && !showPassword ? "password" : "text"}
value={value}
placeholder={placeholder}
onChange={(e) => handleChangeText(e.target.value)}
className="flex-1 bg-transparent outline-none border-none text-blue-950"
style={{
color: "#0D47A1",
fontSize: 16,
fontFamily: "inherit",
backgroundColor: "transparent",
border: "none",
outline: "none",
}}
className="flex-1 bg-transparent outline-none border-none text-blue-950 text-[16px] font-inherit"
{...props}
/>
{isPasswordField && (
<button
type="button"
onClick={() => setShowPassword(!showPassword)}
className="ml-2 text-gray-600 hover:text-gray-800 focus:outline-none"
style={{
fontFamily: "Montserrat, sans-serif",
fontWeight: "500",
fontSize: 16,
background: "none",
border: "none",
cursor: "pointer",
padding: 0,
}}
onClick={() => setShowPassword((prev) => !prev)}
aria-label={showPassword ? "Hide password" : "Show password"}
className="ml-2 text-gray-600 hover:text-gray-800 focus:outline-none font-montserrat font-medium text-[16px] bg-none border-none"
>
{showPassword ? "Hide" : "Show"}
</button>

View File

@ -13,7 +13,7 @@ interface HeaderProps {
displayUser?: boolean;
displaySubject?: string;
displayTabTitle?: string;
examDuration?: string;
examDuration?: string | null;
}
const Header = ({
@ -28,7 +28,7 @@ const Header = ({
const [totalSeconds, setTotalSeconds] = useState(
examDuration ? parseInt(examDuration) * 60 : 0
);
const { timeRemaining, stopTimer } = useTimer();
const { stopTimer } = useTimer();
const [userData, setUserData] = useState<UserData>();
useEffect(() => {

View File

@ -6,7 +6,7 @@ import { Badge } from "./ui/badge";
interface ResultItemProps {
mode: "result";
question: Question;
selectedAnswer: string | undefined;
selectedAnswer: { answer: string } | undefined;
}
interface ExamItemProps {
@ -20,10 +20,22 @@ type QuestionItemProps = ResultItemProps | ExamItemProps;
const QuestionItem = (props: QuestionItemProps) => {
const [bookmark, setBookmark] = useState(false);
const { question, selectedAnswer } = props;
const { question } = props;
const isExam = props.mode === "exam";
// Extract correct type-safe selectedAnswer
const selectedAnswer = isExam
? props.selectedAnswer
: props.selectedAnswer?.answer;
const handleOptionSelect = (key: string) => {
if (isExam && props.handleSelect) {
props.handleSelect(parseInt(question.id), key);
}
};
return (
<div className="border-[0.5px] border-[#8abdff]/60 rounded-2xl p-4 flex flex-col">
<h3 className="text-xl font-semibold ">
@ -45,19 +57,14 @@ const QuestionItem = (props: QuestionItemProps) => {
{isExam ? (
<div className="flex flex-col gap-4 items-start">
{Object.entries(question.options).map(([key, value]) => {
{Object.entries(question.options ?? {}).map(([key, value]) => {
const isSelected = selectedAnswer === key;
return (
<button
key={key}
className="flex items-center gap-3"
onClick={
isExam
? () => props.handleSelect(question.id, key)
: undefined
}
disabled={!isExam}
onClick={() => handleOptionSelect(key)}
>
<span
className={`flex items-center rounded-full border px-1.5 ${
@ -80,7 +87,7 @@ const QuestionItem = (props: QuestionItemProps) => {
<Badge className="bg-yellow-500" variant="destructive">
Skipped
</Badge>
) : selectedAnswer.answer === question.correctAnswer ? (
) : selectedAnswer === question.correctAnswer ? (
<Badge className="bg-green-500 text-white" variant="default">
Correct
</Badge>
@ -90,23 +97,20 @@ const QuestionItem = (props: QuestionItemProps) => {
</Badge>
)}
</div>
<div className="flex flex-col gap-4 items-start">
{Object.entries(question.options).map(([key, value]) => {
{Object.entries(question.options ?? {}).map(([key, value]) => {
const isCorrect = key === question.correctAnswer;
const isSelected = key === selectedAnswer?.answer;
const isSelected = key === selectedAnswer;
let optionStyle =
"px-2 py-1 flex items-center rounded-full border font-medium text-sm";
if (isCorrect) {
optionStyle += " bg-green-600 text-white border-green-600";
}
if (isSelected && !isCorrect) {
} else if (isSelected && !isCorrect) {
optionStyle += " bg-red-600 text-white border-red-600";
}
if (!isCorrect && !isSelected) {
} else {
optionStyle += " border-gray-300 text-gray-700";
}
@ -118,7 +122,9 @@ const QuestionItem = (props: QuestionItemProps) => {
);
})}
</div>
<div className="h-[0.5px] border-[0.5px] border-dashed border-black/20"></div>
<div className="flex flex-col gap-2">
<h3 className="text-lg font-bold text-black/40">Solution:</h3>
<p className="text-lg">{question.solution}</p>

View File

@ -1,13 +1,15 @@
import React, { useState, useRef, useEffect } from "react";
import Link from "next/link";
import Image from "next/image";
import React, {
useState,
useRef,
useEffect,
useCallback,
UIEvent,
} from "react";
import styles from "../css/SlidingGallery.module.css";
import { GalleryViews } from "@/types/gallery";
interface SlidingGalleryProps {
views?: {
id: string;
content: React.ReactNode;
}[];
views: GalleryViews[] | undefined;
className?: string;
showPagination?: boolean;
autoScroll?: boolean;
@ -17,7 +19,7 @@ interface SlidingGalleryProps {
}
const SlidingGallery = ({
views = [],
views,
className = "",
showPagination = true,
autoScroll = false,
@ -25,15 +27,47 @@ const SlidingGallery = ({
onSlideChange = () => {},
height = "100vh",
}: SlidingGalleryProps) => {
const [activeIdx, setActiveIdx] = useState(0);
const [activeIdx, setActiveIdx] = useState<number>(0);
const [dimensions, setDimensions] = useState({ width: 0, height: 0 });
const scrollRef = useRef(null);
const galleryRef = useRef(null);
const autoScrollRef = useRef(null);
const scrollRef = useRef<HTMLDivElement | null>(null);
const galleryRef = useRef<HTMLDivElement | null>(null);
const autoScrollRef = useRef<NodeJS.Timeout | null>(null);
const handleScroll = (event: UIEvent<HTMLDivElement>) => {
handleUserInteraction();
const scrollLeft = event.currentTarget.scrollLeft;
const slideWidth = dimensions.width;
const index = Math.round(scrollLeft / slideWidth);
if (index !== activeIdx) {
setActiveIdx(index);
onSlideChange(index);
}
};
const goToSlide = useCallback(
(index: number) => {
if (scrollRef.current) {
scrollRef.current.scrollTo({
left: index * dimensions.width,
behavior: "smooth",
});
}
},
[dimensions.width]
);
const handleDotClick = (index: number) => {
handleUserInteraction();
goToSlide(index);
setActiveIdx(index);
onSlideChange(index);
};
// Auto-scroll functionality
useEffect(() => {
if (autoScroll && views.length > 1) {
if (autoScroll && views && views.length > 1) {
autoScrollRef.current = setInterval(() => {
setActiveIdx((prevIdx) => {
const nextIdx = (prevIdx + 1) % views.length;
@ -48,15 +82,17 @@ const SlidingGallery = ({
}
};
}
}, [autoScroll, autoScrollInterval, views.length]);
}, [autoScroll, autoScrollInterval, views?.length, goToSlide, views]);
// Clear auto-scroll on user interaction
const handleUserInteraction = () => {
if (autoScrollRef.current) {
clearInterval(autoScrollRef.current);
autoScrollRef.current = null;
}
};
// Update dimensions
useEffect(() => {
const updateDimensions = () => {
if (galleryRef.current) {
@ -67,18 +103,13 @@ const SlidingGallery = ({
}
};
// Initial dimension update
updateDimensions();
// Add resize listener
window.addEventListener("resize", updateDimensions);
// Cleanup
return () => window.removeEventListener("resize", updateDimensions);
}, []);
// Recalculate index when dimension changes
useEffect(() => {
// Recalculate active index when dimensions change
if (scrollRef.current && dimensions.width > 0) {
const scrollLeft = scrollRef.current.scrollLeft;
const slideWidth = dimensions.width;
@ -87,34 +118,6 @@ const SlidingGallery = ({
}
}, [dimensions]);
const handleScroll = (event: { target: { scrollLeft: any } }) => {
handleUserInteraction();
const scrollLeft = event.target.scrollLeft;
const slideWidth = dimensions.width;
const index = Math.round(scrollLeft / slideWidth);
if (index !== activeIdx) {
setActiveIdx(index);
onSlideChange(index);
}
};
const goToSlide = (index) => {
if (scrollRef.current) {
scrollRef.current.scrollTo({
left: index * dimensions.width,
behavior: "smooth",
});
}
};
const handleDotClick = (index) => {
handleUserInteraction();
goToSlide(index);
setActiveIdx(index);
onSlideChange(index);
};
// Early return if no views
if (!views || views.length === 0) {
return (
<div className={`${styles.gallery} ${className}`}>
@ -138,6 +141,8 @@ const SlidingGallery = ({
style={{
width: "100%",
height: "100%",
overflowX: "scroll",
display: "flex",
}}
>
{views.map((item) => (
@ -154,6 +159,7 @@ const SlidingGallery = ({
</div>
))}
</div>
{showPagination && views.length > 1 && (
<div className={styles.pagination}>
{views.map((_, index) => (