From 0bca09f8eff0b795e545b0ed2acd709965271126 Mon Sep 17 00:00:00 2001 From: shafin-r Date: Mon, 28 Jul 2025 20:22:04 +0600 Subject: [PATCH] fix(ts): refactor codebase for capacitor setup --- app/(auth)/login/page.tsx | 43 ++++++++----- app/(auth)/register/page.tsx | 42 +++++++++---- app/(tabs)/bookmark/page.tsx | 4 +- app/(tabs)/categories/page.tsx | 7 --- app/(tabs)/home/page.tsx | 79 ++++++++++++------------ app/(tabs)/leaderboard/page.tsx | 37 ++++++++++- app/(tabs)/live/page.tsx | 0 app/(tabs)/paper/page.tsx | 27 +++++--- app/(tabs)/settings/page.tsx | 10 ++- app/(tabs)/unit/page.tsx | 15 ++--- app/exam/[id]/page.tsx | 72 +--------------------- app/exam/pretest/page.tsx | 79 +++++++++++++++--------- app/exam/results/page.tsx | 27 ++++---- app/page.tsx | 2 +- bun.lockb | Bin 175805 -> 209434 bytes components/BackgroundWrapper.tsx | 12 ++-- components/DestructibleAlert.tsx | 10 +-- components/FormField.tsx | 61 ++++++------------ components/Header.tsx | 4 +- components/QuestionItem.tsx | 42 +++++++------ components/SlidingGallery.tsx | 102 ++++++++++++++++--------------- context/ExamContext.tsx | 3 +- context/ModalContext.tsx | 35 ++++++++--- eslint.config.mjs | 9 +++ lib/auth.ts | 69 +++++++++++---------- lib/gallery-views.tsx | 14 ++--- next.config.ts | 4 +- package.json | 4 +- types/auth.d.ts | 15 +++++ types/exam.d.ts | 10 +-- types/gallery.d.ts | 4 ++ 31 files changed, 458 insertions(+), 384 deletions(-) delete mode 100644 app/(tabs)/categories/page.tsx delete mode 100644 app/(tabs)/live/page.tsx create mode 100644 types/gallery.d.ts diff --git a/app/(auth)/login/page.tsx b/app/(auth)/login/page.tsx index 17117c2..afe3562 100644 --- a/app/(auth)/login/page.tsx +++ b/app/(auth)/login/page.tsx @@ -9,28 +9,39 @@ import FormField from "@/components/FormField"; import { login } from "@/lib/auth"; import DestructibleAlert from "@/components/DestructibleAlert"; import { useAuth } from "@/context/AuthContext"; +import { LoginForm } from "@/types/auth"; -const page = () => { +const LoginPage = () => { const router = useRouter(); const { setToken } = useAuth(); - const [form, setForm] = useState({ + + const [form, setForm] = useState({ email: "", password: "", }); - const [error, setError] = useState(null); + + const [error, setError] = useState(null); const [isLoading, setIsLoading] = useState(false); - // For Rafeed - // Function to login a user. I've kept it in a barebones form right now, but you can just call the login function from /lib/auth.ts and pass on the form. const loginUser = async () => { try { setIsLoading(true); setError(null); - await login(form, setToken); // Call the login function - router.push("/home"); // Redirect on successful login - } catch (error) { - console.log(error); - setError(error.message); // Handle error messages + await login(form, setToken); + router.push("/home"); + } catch (err: unknown) { + console.error(err); + + if ( + typeof err === "object" && + err !== null && + "message" in err && + typeof err.message === "string" + ) { + setError(err.message); + } else { + setError("An unexpected error occurred."); + } } finally { setIsLoading(false); } @@ -63,13 +74,17 @@ const page = () => { title="Email Address" value={form.email} placeholder="Enter your email address..." - handleChangeText={(e) => setForm({ ...form, email: e })} + handleChangeText={(value) => + setForm({ ...form, email: value }) + } /> setForm({ ...form, password: e })} + handleChangeText={(value) => + setForm({ ...form, password: value }) + } /> @@ -94,7 +109,7 @@ const page = () => { className="text-center mb-[70px]" style={{ fontFamily: "Montserrat, sans-serif" }} > - Don't have an account?{" "} + Don't have an account?{" "} Register here. @@ -106,4 +121,4 @@ const page = () => { ); }; -export default page; +export default LoginPage; diff --git a/app/(auth)/register/page.tsx b/app/(auth)/register/page.tsx index 90d6636..7a44260 100644 --- a/app/(auth)/register/page.tsx +++ b/app/(auth)/register/page.tsx @@ -10,11 +10,18 @@ import { useAuth } from "@/context/AuthContext"; import BackgroundWrapper from "@/components/BackgroundWrapper"; import FormField from "@/components/FormField"; import DestructibleAlert from "@/components/DestructibleAlert"; +import { RegisterForm } from "@/types/auth"; + +interface CustomError extends Error { + response?: { + detail?: string; + }; +} export default function RegisterPage() { const { setToken } = useAuth(); const router = useRouter(); - const [form, setForm] = useState({ + const [form, setForm] = useState({ name: "", institution: "", sscRoll: "", @@ -25,13 +32,12 @@ export default function RegisterPage() { }); const [error, setError] = useState(null); - const handleError = (error: any) => { + const handleError = (error: { detail: string }) => { if (error?.detail) { const match = error.detail.match(/Key \((.*?)\)=\((.*?)\)/); if (match) { const field = match[1]; - const value = match[2]; - return `The ${field} already exists. Please use a different value.`; + return `The ${field} already exists. Please try again.`; } } return "An unexpected error occurred. Please try again."; @@ -56,16 +62,30 @@ export default function RegisterPage() { setError(validationError); return; } + try { await register(form, setToken); router.push("/home"); - } catch (error: any) { - console.error("Error:", error.response || error.message); - if (error.response?.detail) { - const decodedError = handleError({ detail: error.response.detail }); - setError(decodedError); + } catch (error) { + // Type guard for built-in Error type + if (error instanceof Error) { + console.error( + "Error:", + (error as CustomError).response || error.message + ); + + const response = (error as CustomError).response; + + if (response?.detail) { + const decodedError = handleError({ detail: response.detail }); + setError(decodedError); + } else { + setError(error.message || "An unexpected error occurred."); + } } else { - setError(error.message || "An unexpected error occurred."); + // Fallback for non-standard errors + console.error("Unexpected error:", error); + setError("An unexpected error occurred."); } } }; @@ -148,7 +168,7 @@ export default function RegisterPage() {

- Already have an account?{" "} + Already have an account? Login here diff --git a/app/(tabs)/bookmark/page.tsx b/app/(tabs)/bookmark/page.tsx index d73fc40..64d64ef 100644 --- a/app/(tabs)/bookmark/page.tsx +++ b/app/(tabs)/bookmark/page.tsx @@ -50,7 +50,7 @@ const QuestionItem = ({ question }: QuestionItemProps) => { const BookmarkPage = () => { const router = useRouter(); - const [questions, setQuestions] = useState(); + const [questions, setQuestions] = useState([]); useEffect(() => { fetch("/data/bookmark.json") @@ -74,7 +74,7 @@ const BookmarkPage = () => { - {questions?.map((question) => ( + {questions.map((question: Question) => ( ))} diff --git a/app/(tabs)/categories/page.tsx b/app/(tabs)/categories/page.tsx deleted file mode 100644 index a54c709..0000000 --- a/app/(tabs)/categories/page.tsx +++ /dev/null @@ -1,7 +0,0 @@ -import React from "react"; - -const page = () => { - return

page
; -}; - -export default page; diff --git a/app/(tabs)/home/page.tsx b/app/(tabs)/home/page.tsx index 61005a5..28f31a9 100644 --- a/app/(tabs)/home/page.tsx +++ b/app/(tabs)/home/page.tsx @@ -1,57 +1,56 @@ "use client"; -import React, { useState, useEffect, ReactNode } from "react"; +import React, { useState, useEffect } from "react"; import { useRouter } from "next/navigation"; import Image from "next/image"; import Header from "@/components/Header"; import SlidingGallery from "@/components/SlidingGallery"; import BackgroundWrapper from "@/components/BackgroundWrapper"; -import DestructibleAlert from "@/components/DestructibleAlert"; import { ChevronRight } from "lucide-react"; import styles from "@/css/Home.module.css"; import { API_URL } from "@/lib/auth"; import { Avatar } from "@/components/ui/avatar"; import { getLinkedViews } from "@/lib/gallery-views"; import { getTopThree } from "@/lib/leaderboard"; +import DestructibleAlert from "@/components/DestructibleAlert"; +import { GalleryViews } from "@/types/gallery"; -interface LinkedView { +interface LeaderboardEntry { id: string; - content: ReactNode; + name: string; + points: number; } -const page = () => { +const HomePage = () => { const router = useRouter(); - const [boardData, setBoardData] = useState([]); - const [boardError, setBoardError] = useState(null); - const [linkedViews, setLinkedViews] = useState(); - - const performanceData = [ - { label: "Mock Test", progress: 20 }, - { label: "Topic Test", progress: 70 }, - { label: "Subject Test", progress: 50 }, - ]; - - const progressData = [ - { label: "Physics", progress: 25 }, - { label: "Chemistry", progress: 57 }, - ]; + const [boardData, setBoardData] = useState([]); + const [boardError, setBoardError] = useState(null); + const [linkedViews, setLinkedViews] = useState(); useEffect(() => { let isMounted = true; - async function fetchBoardData() { + + const fetchBoardData = async () => { try { const response = await fetch(`${API_URL}/leaderboard`); if (!response.ok) { throw new Error("Failed to fetch leaderboard data"); } - const data = await response.json(); + + const data: LeaderboardEntry[] = await response.json(); if (isMounted) setBoardData(data); - } catch (error) { - if (isMounted) setBoardError(error.message || "An error occurred"); + } catch (err) { + if (isMounted) { + const message = + err instanceof Error ? err.message : "An unexpected error occurred"; + setBoardError(message); + } } - } - const fetchedLinkedViews = getLinkedViews(); + }; + + const fetchedLinkedViews: GalleryViews[] = getLinkedViews(); setLinkedViews(fetchedLinkedViews); + fetchBoardData(); return () => { @@ -157,20 +156,24 @@ const page = () => {
- {getTopThree(boardData).map((student, idx) => ( -
-
- {student.rank} - - - {student.name} + {boardError ? ( + + ) : ( + getTopThree(boardData).map((student, idx) => ( +
+
+ {student.rank} + + + {student.name} + +
+ + {student.points}pt
- - {student.points}pt - -
- ))} + )) + )}
@@ -232,4 +235,4 @@ const page = () => { ); }; -export default page; +export default HomePage; diff --git a/app/(tabs)/leaderboard/page.tsx b/app/(tabs)/leaderboard/page.tsx index 33d2e2b..ab7c524 100644 --- a/app/(tabs)/leaderboard/page.tsx +++ b/app/(tabs)/leaderboard/page.tsx @@ -2,6 +2,7 @@ import BackgroundWrapper from "@/components/BackgroundWrapper"; import Header from "@/components/Header"; +import DestructibleAlert from "@/components/DestructibleAlert"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { API_URL, getToken } from "@/lib/auth"; import { BoardData, getLeaderboard } from "@/lib/leaderboard"; @@ -40,7 +41,14 @@ const LeaderboardPage = () => { setUserData(fetchedUserData); } catch (error) { console.error(error); - setUserData(undefined); + setUserData({ + name: "", + institution: "", + sscRoll: "", + hscRoll: "", + email: "", + phone: "", + }); } } @@ -97,6 +105,33 @@ const LeaderboardPage = () => { return result ? [{ ...result, rank: sortedData.indexOf(result) + 1 }] : []; }; + if (loading) { + return ( + +
+
+
+
+

Loading...

+
+
+
+ ); + } + + if (boardError) { + return ( + +
+
+
+ +
+
+
+ ); + } + return (
diff --git a/app/(tabs)/live/page.tsx b/app/(tabs)/live/page.tsx deleted file mode 100644 index e69de29..0000000 diff --git a/app/(tabs)/paper/page.tsx b/app/(tabs)/paper/page.tsx index 6e3e323..26ce11e 100644 --- a/app/(tabs)/paper/page.tsx +++ b/app/(tabs)/paper/page.tsx @@ -2,7 +2,7 @@ import { useSearchParams } from "next/navigation"; import { useRouter } from "next/navigation"; -import { useEffect, useState } from "react"; +import { Suspense, useEffect, useState } from "react"; import Header from "@/components/Header"; import DestructibleAlert from "@/components/DestructibleAlert"; import BackgroundWrapper from "@/components/BackgroundWrapper"; @@ -15,7 +15,7 @@ interface Mock { rating: number; } -export default function PaperScreen() { +function PaperPageContent() { const router = useRouter(); const searchParams = useSearchParams(); const name = searchParams.get("name") || ""; @@ -23,7 +23,6 @@ export default function PaperScreen() { const [questions, setQuestions] = useState(null); const [errorMsg, setErrorMsg] = useState(null); const [refreshing, setRefreshing] = useState(false); - const [componentKey, setComponentKey] = useState(0); async function fetchMocks() { try { @@ -45,12 +44,7 @@ export default function PaperScreen() { const onRefresh = async () => { setRefreshing(true); - await fetchMocks(); - setComponentKey((prevKey) => prevKey + 1); - setTimeout(() => { - setRefreshing(false); - }, 1000); }; if (errorMsg) { @@ -124,3 +118,20 @@ export default function PaperScreen() { ); } + +export default function PaperScreen() { + +
+
+
+

Loading...

+
+
+ + } + > + +
; +} diff --git a/app/(tabs)/settings/page.tsx b/app/(tabs)/settings/page.tsx index 4068bd0..212def5 100644 --- a/app/(tabs)/settings/page.tsx +++ b/app/(tabs)/settings/page.tsx @@ -3,6 +3,7 @@ import BackgroundWrapper from "@/components/BackgroundWrapper"; import { Avatar, AvatarFallback } from "@/components/ui/avatar"; import { API_URL, getToken } from "@/lib/auth"; +import { UserData } from "@/types/auth"; import { Bookmark, ChartColumn, @@ -22,7 +23,14 @@ import React, { useEffect, useState } from "react"; const SettingsPage = () => { const router = useRouter(); - const [userData, setUserData] = useState(null); + const [userData, setUserData] = useState({ + name: "", + institution: "", + sscRoll: "", + hscRoll: "", + email: "", + phone: "", + }); useEffect(() => { async function fetchUser() { diff --git a/app/(tabs)/unit/page.tsx b/app/(tabs)/unit/page.tsx index 8821005..47b227e 100644 --- a/app/(tabs)/unit/page.tsx +++ b/app/(tabs)/unit/page.tsx @@ -16,21 +16,18 @@ const units = [ const Unit = () => { const router = useRouter(); - const handleUnitPress = (unit) => { + const handleUnitPress = (unit: { + id?: number; + name: string; + rating?: number; + }) => { router.push(`/paper?name=${encodeURIComponent(unit.name)}`); }; return (
-
+
diff --git a/app/exam/[id]/page.tsx b/app/exam/[id]/page.tsx index 921dfb2..0abd813 100644 --- a/app/exam/[id]/page.tsx +++ b/app/exam/[id]/page.tsx @@ -6,71 +6,11 @@ import { useTimer } from "@/context/TimerContext"; import { useExam } from "@/context/ExamContext"; import { API_URL, getToken } from "@/lib/auth"; import Header from "@/components/Header"; -import { Bookmark, BookmarkCheck } from "lucide-react"; import { useModal } from "@/context/ModalContext"; import Modal from "@/components/ExamModal"; import { Question } from "@/types/exam"; import QuestionItem from "@/components/QuestionItem"; -// Types -// interface Question { -// id: number; -// question: string; -// options: Record; -// } - -// interface QuestionItemProps { -// question: Question; -// selectedAnswer?: string; -// handleSelect: (questionId: number, option: string) => void; -// } - -// const QuestionItem = React.memo( -// ({ question, selectedAnswer, handleSelect }) => { -// const [bookmark, setBookmark] = useState(false); - -// return ( -//
-//

-// {question.id}. {question.question} -//

-//
-//
-// -//
-//
-// {Object.entries(question.options).map(([key, value]) => ( -// -// ))} -//
-//
-// ); -// } -// ); - -// QuestionItem.displayName = "QuestionItem"; - export default function ExamPage() { // All hooks at the top - no conditional calls const router = useRouter(); @@ -280,13 +220,7 @@ export default function ExamPage() { // Render the main exam interface return (
-
+
{currentAttempt ? (
@@ -301,8 +235,8 @@ export default function ExamPage() {

Skipped:{" "} - {currentExam?.questions.length - - currentAttempt?.answers.length} + {(currentExam?.questions?.length ?? 0) - + (currentAttempt?.answers?.length ?? 0)}

diff --git a/app/exam/pretest/page.tsx b/app/exam/pretest/page.tsx index 760245c..9042e47 100644 --- a/app/exam/pretest/page.tsx +++ b/app/exam/pretest/page.tsx @@ -1,7 +1,7 @@ "use client"; import { useRouter, useSearchParams } from "next/navigation"; -import { useEffect, useState } from "react"; +import { Suspense, useEffect, useState } from "react"; import { ArrowLeft, HelpCircle, Clock, XCircle } from "lucide-react"; import DestructibleAlert from "@/components/DestructibleAlert"; import BackgroundWrapper from "@/components/BackgroundWrapper"; @@ -18,47 +18,46 @@ interface Metadata { }; } -export default function PretestPage() { +function PretestPageContent() { const router = useRouter(); const searchParams = useSearchParams(); const [examData, setExamData] = useState(); const { startExam, setCurrentExam } = useExam(); // Get params from URL search params - const name = searchParams.get("name") || ""; const id = searchParams.get("id") || ""; const title = searchParams.get("title") || ""; const rating = searchParams.get("rating") || ""; const [metadata, setMetadata] = useState(null); const [loading, setLoading] = useState(true); - const [error, setError] = useState(null); - - async function fetchQuestions() { - if (!id) return; - - try { - setLoading(true); - const questionResponse = await fetch(`${API_URL}/mock/${id}`, { - method: "GET", - }); - if (!questionResponse.ok) { - throw new Error("Failed to fetch questions"); - } - const data = await questionResponse.json(); - const fetchedMetadata: Metadata = data; - - setExamData(data); - setMetadata(fetchedMetadata); - } catch (error) { - console.error(error); - setError(error instanceof Error ? error.message : "An error occurred"); - } finally { - setLoading(false); - } - } + const [error, setError] = useState(); + console.log(loading); useEffect(() => { + async function fetchQuestions() { + if (!id) return; + + try { + setLoading(true); + const questionResponse = await fetch(`${API_URL}/mock/${id}`, { + method: "GET", + }); + if (!questionResponse.ok) { + throw new Error("Failed to fetch questions"); + } + const data = await questionResponse.json(); + const fetchedMetadata: Metadata = data; + + setExamData(data); + setMetadata(fetchedMetadata); + } catch (error) { + console.error(error); + setError(error instanceof Error ? error.message : "An error occurred"); + } finally { + setLoading(false); + } + } if (id) { fetchQuestions(); } @@ -74,15 +73,16 @@ export default function PretestPage() {
- {/* */}
); } function handleStartExam() { + if (!examData) return; + setCurrentExam(examData); - startExam(examData); // Pass examData directly + startExam(examData); router.push(`/exam/${id}?time=${metadata?.metadata.duration}`); } return ( @@ -192,3 +192,22 @@ export default function PretestPage() { ); } + +export default function PretestPage() { + return ( + +
+
+
+

Loading...

+
+
+ + } + > + +
+ ); +} diff --git a/app/exam/results/page.tsx b/app/exam/results/page.tsx index 0ade0f8..4052af5 100644 --- a/app/exam/results/page.tsx +++ b/app/exam/results/page.tsx @@ -1,13 +1,14 @@ "use client"; import { useRouter } from "next/navigation"; -import { useExam, useExamResults } from "@/context/ExamContext"; +import { useExam } from "@/context/ExamContext"; import { useEffect, useState } from "react"; import React from "react"; import { ArrowLeft } from "lucide-react"; import SlidingGallery from "@/components/SlidingGallery"; import QuestionItem from "@/components/QuestionItem"; import { getResultViews } from "@/lib/gallery-views"; +import { Question } from "@/types/exam"; export default function ResultsPage() { const router = useRouter(); @@ -58,15 +59,15 @@ export default function ResultsPage() { const apiResponse = getApiResponse(); - const timeTaken = - currentAttempt.endTime && currentAttempt.startTime - ? Math.round( - (currentAttempt.endTime.getTime() - - currentAttempt.startTime.getTime()) / - 1000 / - 60 - ) - : 0; + // const timeTaken = + // currentAttempt.endTime && currentAttempt.startTime + // ? Math.round( + // (currentAttempt.endTime.getTime() - + // currentAttempt.startTime.getTime()) / + // 1000 / + // 60 + // ) + // : 0; const views = getResultViews(currentAttempt); @@ -98,11 +99,13 @@ export default function ResultsPage() { Solutions
- {apiResponse.questions.map((question) => ( + {apiResponse.questions.map((question: Question) => ( ))} diff --git a/app/page.tsx b/app/page.tsx index 805acb6..47d33d3 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -53,7 +53,7 @@ export default function Home() { className="text-center font-medium" style={{ fontFamily: "Montserrat, sans-serif" }} > - Don't have an account?{" "} + Don't have an account? Register here diff --git a/bun.lockb b/bun.lockb index 90fb462ed520e9b57f8ce136503519275112b3e8..ff2e50db4d0439bfd9bfa31f563fecd98c4c821b 100644 GIT binary patch delta 51730 zcmeFad0b5I|35sZnluqo+6YBd6xk-C5uuC-ks`Ed(WXrylRZneBU8w}lw`{mvSlgz zQYmC#vhPcX`}sQOjL&>OkI(n}``-8cxbMH-=ixce>-D;>>vg?e*Lu!0Gm5HpS3ha6 z%-%L;K<5abC&`trH{azJ+l>A+Nt$~)FWtFKYRa%m2lLH~7dmK)=wdXMk8Ytz`N?n+ z|I%0_N^J^-1RNXe9~cj{k03_{Cy^-nsv#1YA^)zx#b8s&d0-PTEAVu%G31G0Bk*vr zAviA7KQ;z-`sj#6mI^3ZA<+uVfLnqe*At0afX@oNUEqaabLgYM&B4P3b_A0{t-$1f z1WXNo(&ihk7Wllt`@l_+pAA+}VgVB5S%Sd+0(*k>p`YAHB*MSc__&C$0C?UX`udRL zf+G`yV@0CC=!j^LC?DOY2mLRwE_frj0eBXSGz6D{sonte1nKQGL<%lpAt;Q&RMFUg zFW3+=4Mj|JTzp_$oJbR1QUkTfr*cvLQPE*RB2hp>TtIZPNF+uyWLSbBBMyv+OM+jb zmjLzbS}+At0j7X&8lnHGD^Ec|J#iHM#*HOp8nd(LPYduiFd5i{OzPTskXwPH!6d5; zuRuL=Y(AJGRXLsjxe4T8^k7uHNaPA*RBpQh3G`BGL~vph%80UsjD-TP1XB+L1qa92 zg~f@wL8b^g2zn*FBl#zo488$VSGPhw^}tU|Br9-OoLxvnbbu(bHDB&M^yFYzSV&ZK zY_Lf5R8UL|iwd$!h!JgR!xzXBIL0m_I?z8JUPOdNhQ*7rV3iD%qaFod+m<&xF~UDS zJ~&7;DL6Ti0;+(X%AI4cYZ~{s-i|L792XNCj8+R!8)KO|*qRU1KQKC$`e8ftgPr&W z@=*@+By};E>`Vk>7*l(LF(9c;!5FaAcaG32kT`=xd+=&7#wIlmjFC$oa`R222$qgJVO2G2W`>_Q#!%Z0KNqu51TWgP*{(uHOmy z6aC}jCnJ#9;DoraiOHf~aF7BL4M6`RxYQbWN}k^oB3G^J8bsU-`q<#O=!nE%k)|gf zS?y5X!5lDc7+5{gQ?T@U598-bP;fv(2pYU9^w?A|*;hp#2-z62hXM&BB<#KTu6+om zp|BavA0BhT6v^aKygnp4CJ1&#?I2T++#Jd0UjUOl%ZKlQA-;SA6Qg78a1w`R3iabr zj(Xe-GEHj5S0t!`!^mg?J`V@T(6q69mwF(lx-6Ds7u07b3?gXBntKi`ljfR}T@RIz>_ z9|0pU8i)&y7m2`;{xQVT+!3+>ng}>(lzr4(1h_s6Yk! zPvj%Lh6vzcs)0A}*gfRaSbu;_k~)HUqB$Q_sx=lLO&kWAJp;qplwq zj-gT@u^$QQ+AUy;P>h1q5DS?c@P|xYHylh|>H;Qxd$18$FOrur1MP5d#I=B)8aRQ5 zD1SGY>~03r>R13KI|IN9T_idoVFdn+v89YhV2Wfjm^@!1@C+~+>IbG((H=|@>Y=Bo zya-GUkBa5>pGNSqIhHi(C148l7}x;3a)f9U7irB1zKhGjlyM$RJ>Z`dY=<)~xKNNc zfsLVG2qwpSVD*r}szkng@Dx76v22g}Efp69?P0J1N(Ds+#>a+51+Rfji+(wnth9v{ z%0D}mx4sih+nj3AXF#TM+$}9`Bb=u5;dBI3e;TIn<1-)S$m<|5>?u-TAcOj4Fcj2J zT?G!q0;Y=7G5X|wB$)c$3rzZqRDL{y;{yF7{DXqiAXERvgQ>hb%F$B6TIa^Y15EXB zlNX;H6D;Zrxjs$p-?R7(dnl;lp4q%ZL38<&!Uar6RSSWC&EYrnJ7C(-j|jXP+zRqk zfyaPbLhcQwqqC*J8er<#cz?_Qw!A@mg~>v`Z`Y$`nzP>*35#Jd-^VY&G?i|EX=FzT z{4$*{SA+62<>LKg`~xC_MN5|Oq2GnR0rb}dp1+Lm;s`L=8w!Reiqwuskn09ujC1Ob zrF_HHU~=^|m@4Fe&A`jSuBMfXRU&0>}9$ zQ3r_PLt~>8LP9&QTF2L~1=BQtCGgVqe7&%!(BRmx__(;3h_Lw1B2lt`!c;{B45V)0 zBb)-JeJmz8DhO&3%EiTlQ#bOCOaW7V7?>Iei{eH=WDiG4ZzZrQ(wNxj$e8#zk>_T< z{-lWD1k5{8Qe;GDxSpBcQg`R6XPEjm+T+G^&!bq@pP%uM^3_>TMQm8>xJS_K%W$p=s9ZWoZfpS zuC42$Ci@yUuCI77=Eo|JYi$xIEp6fU-oEEZ-_vfg5PkXnkvCtrnHt^L$K3K^>yr}8 zLSJU>odMt0$J9kIdtxkIZ|{tD*dOsWaNX-5-;ve5PP!g?FEKFCoIlTHylkLW-kNuh zJNK#Ivtl57e0W%~#_yZUbk-i4H9ELf$6!}_lhL=1+7|ad>9$&9jAGJK`xT!DR~Z`F zuHLn5%hZTz$2vTJCS5gJdN9|t!G!9%1izFW-F#*=u(Xxl(cWM0=lEZ@25I#7x{@X@ z&M9|%ka%yPeqpP1yBjv!GT-@@W5xQft-g0_T;{xfkDWYlUi{s&!#?Nf7C$mQ_Geec z*TiEd%p;Ck&hOOv`=`zRGbID^4l1^fzW7Ew+NJG@?wh()3|YUmR(kZtvw)nq{qrsE zbR5@ioW*nf)h~@r?w5s}>29y_WzqJX5$megBp4M2A1f^jei+h8=DOUx$hKGJs+_Vf z=|Ai}HtcDhaJoy@H(P2v8aM3i z+n7;@7il}S-*&Z0^zZ|U8}phZoiwx3K01EFgLOMryfSKfWA6WH?0wF4AO3Ps?du^O zPj524P;6-0q)CKV>qcd&u~s?U^Upk^JaM(g)Dwme4E3AVCe>_S+4A?NE|%}N-f>-7 z^(d!jR_}>^eXp~x-Y8d2`WB%Pr@bsCFtcaUN9CRhv*{Ke*S>W;%8YzztY9-Uq)@OaKoSD9CmX(ZI##UL$ zB=>Q4IU^JiYtdZFn6o}@WlRv8h1U&i6<+VKrtM^sp*XpOQdUxNI;3vY&CF@Gs+~-u zu_0?_?aT~keXM27S~d%>*V!s-nMA)S-uP4X6ElqUv5`rRLe~j84c6aU%DiH$Y-Ffu zYAchZW6vJIm44WUT*!1aa>{V9dUAChwv{q%SfBPX@iW|^OtiPq zS2e?y&czkMRzW+2HMN&9hgctbndDDP-fi@gtyH69EB3a%vv^V~k*F7IZtcWuXMH-# zBo85X=RL8KN{lW3#$zv)_(AH4JWY0^t(4iu`gD>>#FnVfmA9~xYPeXkw>vpAb6Had zndB_8a1dbTQkJweyV}87GNmqQh11+Lm`m zi}i2k$X0cgAuQ8w=mzMijwbmoY1xkN22B=&Ckci`U8KQobdZYIki=G_1Mjdt-DMJE zywTDPxw`B_blFHqLm+9f7S>Y9W=Pb52(YaqW5fDNoh4mt_z9-Nm7flYO2D7)QprI` zG->!ywQWVB&b%#Ksl*Xd2S^&6PeG8#-+HV+THOnYrXf5;7iqN@(fv7M@gL-<1Hq9O z;7}$cG6^G{q>>ANC93vIkkB}$%fJro>YmP$MQD<$^BxsLqEnYq+u7;%AlCtAW4ze zkh-5yus$v_$zkXQLnmUfGiYdaWpBG+Z^vvO1f>RR(N4*XxKun+QC#W~Qt@1BRBx(gQpW7XSoW$vNA(hBh_v@u4 z!`(EeTKPAnh0fwD#3B{W<=#b#b5z_H{zdRckm7Sq(1m=Bc}Vg0z9PkyVLENVEjS+$wE6nDzB^W&+qOPno0u^NpNE=k%0H zG(33s7@B|*H%J&a+9BZjd`Os|DEY8AmMkP%4%{7w#(NKTwcJ@e0JG7P>y1@Nas48> zixl~VYS?s)hVp*F%Ry3R5bHBS#++iaM#vwB8 z5f6gYm(Dq6F>5+fCOHn-7xfTZPpNq9aN!P0vKuLCmtU7JAkoT#7dBFjZX;OVQO@E> zFB(uRV&*klHA<$@b2Mw_?JQn7ntaD{klaR!`i;M>GV$S$M!u`uAn|db_r*&f;r;}t zblG1$ZO#qh8sEKCDj3ozvdkQ1eSBn+Pmp>079*t+hcWydKx^pyNJyCE)HdwwhD7s* z_v8yCJ{q#>G8Ue&)tD_ykfIF%Q9}P7(r9j3yWy4&_h42|;!LExSaUBYiS{^t2j(pX zK>BN6TmlLAak#7S7*DqSI`q;(;$H5R}RhaUrBp+<-L-=9dxQ5hEax^Zczx-rqc$l<(NA0GY&R zB7az+liNtexsV2NJ@E-CXR@f_5W=nwbe1HBpb5S~jOQgtd_T}a(+*YT(YhWBi62k8 zS;&Djmc~;$`rZ6Z{IOlNyEB)%lw&=f+#dOsDS3ql{x(c?%BKox9r*lDz))fYk^NP+=` zx)KtV1;3gA~pr{#HW6A%QiEc9w)EsBTXl_HxvLz)u}Au9e952Y1V>(I=5L zi*c41Pr;w;d8edOW-^->?FEh2+Tkc{+*BlW+&eb@WiAx}tqrv!<~!iBU2?mRy%IL)a|n_CSYw4eE3}c|2uJ<75)8X(|_KY51~PaWdur zTZNpD$iZDfY9rPHcQJ0$S>JeP$*k$BNkK=~8A$wur9;6yg?F33Y4V1|J7p1tJ7-8k zXnje3B1K~aXFa9jfit+rPi$%H`BXY0lP6k zDp?AN_8R`S|16{)kPPXN)X<&H-cEKF%V#4Z*4)lXd>W}fTx#K5y3-=vN2CI{l-E2} z>L5}RIGxday8WPX@qbe{kQ&SB?9)`Kg-GEnL%AnNjpkCl7N}Ayks3uh$xEc{;UFFk zTT8|57s7e&fhis-ni%}EOb#Sk&Vy$#qk>(woB%_cT#My^MJb`DW%EW)58$cti(SHeRHq%*>wM4ax z<1~|8g(Tw}a+PW{T*}^_>CAYtrn6+?^~>;i9@c!8llVJQN+tIp$$7~~DoI+!PZ|E^Y#$^#$h6swog8%pfxC+ncU5v*B2K$SNR2`v zZY@h5LUM-0KajS}R9`SYkjNard^SR&rN!R~KZHc}Fsxp9M9mV3`u!~#0BIax^s~Fa`k9G z(pk$*empa|Aw{LI3t>yA&)Lw(A~tl~2}rcBan{96vJo5ioHY?CvcM$aa zNE_(N>Nji0UTdx+`G_2f7|&3)QgNqsnC^7vBN>MjwTV*+5p9J;(-!^WAl0b1p5Bp= z$k(fO9=hdP4T%h4V63I$N=VpDF))oc@bjPBUi4fcbwMc&IH3zC+LIIdi=lOa7D^H2 z=yC2f9wE`$pz2AAyxvCjJ4z{}&YVw_w-8cqNR8-TU++95ZUkUVj9x=CUXsR}_#)7W zbO36<9oyJaqJs=tvK;bJ+|;I^UobqAW=zeF7Fg1FU}^m3Q5zg zB9Q}Ek8FlRqR!@L?OsUaBfRo()DZ+-?wu6pZFC;cTRfYPqVoU`L=HT5iNK}uHb1S5Ojm&*W)LYmopy3>dByJJTfjMvd$DbT zq=9MEMPUSE1P#Chpi7-84|9sU{@*a!#LD9?HAXk!Qd9h|WrmGp+nF*bFX!s;OfqJN zNW}WhGggp! z0Me%lJPSjG}y>AWBQDzs03ix%NfS*Wbu+vl9+NY3-W(r%D*Dy6H^CVCz-WdW$Z%M z$^a@=4$wtRas>&vh^bZ;K>FJNT^zH$7Z}s0T1C|W<=zA6A}0AhpvAsfW$aJV6M(M& z4U^SpT&*q zrpf{vg3Tb4su_bnW0Rp4f}$mu3|I)VC6&VU{~dD+=l@v)P&&O74*cE zY=;+G5naIbz&!^eQ6Gu^U@fpa82?0r=tW>MMCC~3A@C4^hk|Jhdx14ryDWaA8ZG3~ zN7U(3XHxl6Y4BKq$AL*do?iZqNi{*x6H~naFfHUzfun?cuqD)ZvlR*&*vSIN3k5i4 ztFw&VsZ=7glrcr%B!QE`6x0mL6u2>X31qUm9NZmz9$b&@y_(Dtv=XVf+#b z{t;LMtx>)Sj*SXbZ%U^1mgnF3S829R~Y&A@tKYcSdApn+LTW$o$Z|G;FpqfoI6 znB=Zts@fedwkI$}>?-6FQ~mz5i_ml)EGRs{WQe}nKov#`JQ_?Mj|Eft zKrsG^g7HG-LcvroQs7uHl}`lIMNG*gyioaRf*hQJ1XY{`CXds=RBnBJlFzM-=AjIbd zz9^Kt1f~Hk1Jm`tUAwj46-=UOrxpS5(+Y4DXR&q};jZ+^mjaz>~mV?QG;b6Md znd*?KbXVOm- z^y*BGO&9d)O!^e)(K8Cs93*Hg<_Vl8R8VJ9EfVy^R6bph)tL-0fu2sFEFu4YW{m&; zT;PAs*GEG;&=9qij~BY#JSfP8U~=dPWeQBmB0=Vu&TDlA<((7?{3oX6Rf2r#feT>j z`fEaYV$$CL)8wiU__mO*&Q$I$^xShmH4>z_52mhr2`0~9f$1V9`L!Sull%rus`rAP znDie6`9CqW^GV1D^C!(`K|xH(Z-V@PhpEaByifzbg!<}C`rm^7Uo4z7Tz{beF?Fdn zm@4Q{KMG8dNgxyJ3VLFa>w~GDKA74t6!cBObP z0X;R?228S*z-@){#AMf6kpG>L#XZ|{54luO{lS(pNj3uh-#plo9jgD|Jm6Ax_kSL6 z|9Qa0JSYMFdBEi#bZI%(P=5gbJm8`e)SqYR!2BhlimE#*QVMo1L zlVf`9Wk@4f(_`Lj38a)`a%Lo33Mr{bkF_h3Gu~`+kvD67T#v1WH(LWK`=p%lXJ0_dJf+8aoRTwvY}P4n z*6p+&TL&qabwBOReutEQTF!*9wUBa(^;o}RITOa_79+khi0_P?nZ){>L40QsAEZcD zau)GHiaaaFw~GoPg`Puv=j8abUD!FqcOLOUiepXBBR)te=jBWSTM8+u1o4%~nJH{? z3F0e7e2|h^n^MFFDZNzAOk=AcrCmUL7v%Wd`GO0G?;_%Zq+okoM0}94FUpyj>wUXwFR*+NL6*Ad@!IkTJ%yN>v7AU;SeYkC9mK}xwH$G6!^Atl{J zd^hEcl1;ve_{tC;q%78^4DmrqFOxHC*eXbA<%q9b&SbL-$`M}$;)ArF?NNdFAZ1s` znT_lVNSU_~-z_=5q?C2bo7uvC!0T4ly%Nz?BDzXBzI{|%>CNP@!>YWQTs9Z4J6Ptn zH$FY=a~qM}L1ee(Og<~QRs*)m=GLz?xQjGyB=e zcs;ruAqKH_Ji&N9 zku#-i?h}mHQ;gSBIdhTqd5Wl>Au33hS;;eu7o^B%a^@;q2r2YA#_PEp-$Dy}j>u{d z8Kj%6X${5;Qc8^+pI|J7l=K3zy^u4v*yI-&ua_7vNL8%OON>q1DO9}(Y2 zIrD)H`-u2HAwEc-Skq644^qk}Ilc;03MuI`;`=OTzOl)l5#JZY2dS2|`GWW$rGJq# zKiMisXan$ua(*DbA96;*=Ker@KM~(gIa8nY`HA>`AwEcYtmGHsgB1Bo&NN~R zA%)fkfV{^Olt4QthqOfzg4 z?~F=DFC(3iGtDxp7;j}7!{{k{Gmy=d3m7OIHK2Gyik8YA8c@`bB3lCr3*`$^WNJd; zp$SE6WtJusZdy>(k-|#pt_8(+Qsiqv(N0-QiX1T%eqtzWl(}LkMr%W%rwv7WrH?ig zI`yC^B88n&QV)v5q=>8sMMq^JDMEFiFxP>?K^dk4g^2`;%cSU{G?hS6LW&d#6y21i zq)5_*!cG?osWMp?3hVk%RFk5I(xyHXm83|o4~3JmiWF%Lpy=HIir&fv4WMw;V;U;o zHed!ZGGz}vC~EXjB3ln7T$C?Jk=YOmkA_h6Q)V@U!mSY$b)@L8bZ-R3cT(gxf?}Yu zmJ~USq3~-Ag}XAhF%+Zqq0rNZLay}DheD?b6h)-)P)eFWahMd5O`sU6EF?v!0Tkv2 zPz+Os89-rT2*qVmj8K{yLQz7B6hkORDoaU`)D#N4rcih*lbhnj+6anjQurusjG(9_ zMY<6bW0X~-z$b0cAI4DlDHj++;b;QI8&Zr{_Ar5>h7{Q*Q1~lfkRsC*3J+5#0+m^& zP`H^vQAdhkrMnpv-${{g21SUnmJ~V7pzv!3MVKLcG>0NuSxAb|7EqYCfMT*TtOXP%Eupwfia4cdODIZ6k11Z62Hl3GDw z*9wX$%H&p1SX)3*O^RftjRh2yq)4}bVw$pw6ls=F^tOZ|MY+I|@n&Wy9}+2)Jz9fO zmCK1{Dqj%IQo6JO%~ocClx|k2TGs|u=PKQ;pr5DQNHkwrOO&P@))urtnM<@#$+QD4 zQu=_DI#|NWqIRg7u9R3qahMd5)=(@}7Lp>=1`2Z`ysejwVabngV(rrb!hU0DlK<~YEt zp99S1Dsvr}q4>iae}lS1cq@(`542(j>PIztBDN{r_kh(oR&%m>bZXa<+FLr(eHXsp znO6SwwrAwy!rT4toQTa_Ica9&(tY~Fo;drZT)b)2!sC|ADh;+&cJtKj4k?KY_3E(c zeNwj|m(q&MO?TQVlsk7R4<$#GtSX;qq8RsZ%KeeAjQb^eyxTu6A-dsC`@ut`iN$?0 zc4iOk{kv)CJvGBS)eTplUJ)mAKOcQ{cje}#BMQts`=ylbef_wp_C06WnB@aqSB#n8 z_SUaMotHEWOdh}DS>}RIdg5rKtGS*E^P}COtS7xzGn}t(*ne2s(lE2k{p|UM>3cJ4 z>KWfSUHZB7{J4D~j@zGfFTWD!{48PH_;f{6zcWAmTGXa{J?ZJ!R{Me2?!x`Ei}rkZ zp=NiNy4^rU)7}<$cep4f=`IiHcjv8p@{S{~YB$C$+!DgR*qFC_s?_vqpNvsQ+8CF) zJ=h+tKQCzfi5m-bUhlYC=vr`Kpcj3LM%Bf;)eY}>NMCUu}xfQ%ecB#3ir+G-T}>y2Os7hU=}PL<(3 z>V{A5T-IMVu$^3Id;XmKwti+Mm#ik4ZC|b5-Fo1>xAR_S4Ea-8Qg3VLsULqIn6_j` z;e(H|yJxmklm>SyYV&Gg+c(c#-jlTD_ zDIJv9p)xeXzIh|5>)|_Z<9C@&@za^S`-Z8r&5~1{BoRK34iBs8sXtEHSIzLge;F2O zIapU-KAY~S*Wx7e;%YyKtuv!fH{7?>&ZF7%$f*kpUM*>=yY#xVLh|a?hk1u;*50!m zak-J!(fH~vH@yflbYwrR}fvzo45L+3#%7#w)Y;9lbYc#ORe{KCWF_{pPW76AKbiMs* zfnxubZi9XorN`^qHXOTP-+PnQoj!G_l08q28?TkQcWJm^v+XnTiqs4rR5yG&=4;T< zt+lOB|9;uz#+Q2rH@^HFY#(}IVaBgI(OK*Jrz1-C&-AevYi>Slm;FIIWw+ZOUsY&- zTQcs^DV+(Ymo>v@gXs_N{02nbqPn!czYKKWaqLX#c}OZXLfYzSnSL)q~aJTJ4xuWoxE6dD}?6*FC-u z?Xr3A*Gpk@$5)rB87@>eoNQ5FYslr%*WJv!->e_{sPp#(QImO=%duQgxbQ=>hna{F7~7yWgL8|rRrsCf6n{*lMUf&8cE_?|eTZn$l3s|&k6XhS&yH6h}RQ2#tb-Nw=*HDThb?M% zq|9b))5>X&mShfav0J&isp&MWqEYofOqf4m_w#e=J#kFk@G+Zd`^GI;tbexne&55o z^H+73Y(3TM!@T)b8HXDmo>jNjLAfU2^o%x5Wb_sy%7!iS;d7loZzKq_xNACcwbO!Zi#eaNUl6Z_S$f6ZnK)r zF;^VwAGPec*D33C!}|qY$}7@~zWFx%lveh;?~gTwmw(R;k8|=^P&vnRyT?BmjcGl^$s&I-G@{=W0V z@>#R5j_;mwe|_MTcK29YKQ+Ur)$Lxtdm&Eqxcp-G6%Ug`ZX5dKdA4=-%KN@!o4&%% zVDd66uVDevZyF7Mkm0d1JL#Zi$;G@i+m?mf_lR5m&92=mzbbN4)e|&7RF~GZRS6RZ zO*!Qna^iyRi~7^17uP(w5Eya9aN};5qd|_n)~wJA?A5ZQlkpT!rvoFB-M;-9)UB0y zjG4=*;8m@LB@Q0WVEk2v&!`*r`*6U}NZut|a%=FwZ!->@ZZfQX+5C_`QPFAX15Dgr zc^+$9*P`Ni!q-Xb&gbb_w@6s9U(b_`3P>sJ{7h@;k29~-96qaVH+PHj&J(@^tVBD*tLbB1-H>TyB&!7=N? zbnRx&Q16Lz>W2F@{x!L7-Kb4NoSY9`-Q*^{7FRSzmN<6Bq~+(_^lQ(RO^^RM&cd)= zz2<{<^c%6jd2>Y9voAZf%vapAb|~)mMSqR@F-ji*R$W@B-L!}1+RuE_=GX6xkZ<3I zzO7d;-DQ-fOWv~C78zsud2BE07Or76BC6Ek+?#uvU+P|tuu7S|XYGC)>7-hNvBgc) zdZI+#?$f#_m0!;(%+_b_-xTP$C~S6I-RIewpJqBXe|2|;HyfQoR4T)w8Y`@W;%#eLszP9IPK+%@Vc&($GSF7UDs&;;|}H#g%9ih zygX`q&&0^|X@!|p(m|twe&#&7nDr-rq~b`giMbYYXK1eN{-o;DO3BFz&+Tf4FQ^;- z`tJ3*R(XLjwLW*WT&JHmn0au5)yoZ0{Rbyl>ex52IP$~qbd@;0N9k@?+wLvcn}uf{ zbXpvKyi7SjOY3OAvb7u3>|RvarN2vPHGW^4H%3`9)_sfZ#kK9atxbM;PjhumcbAPP zb<)z=uvV#WJQtMr?dOoAacO~Z_p>I`d;5j%{$uCR_>)Om)ZRC7YKAYV43ph?uHGL! zMlD|M_x`A6yNG3f#H-HkuxWCV88Pzyva6yWy`4X;Q1mfd9eHbe`|{8UHJh46FKjbn zv#oN&kfpD7ZU3dqe{h^XlrF0q-j#i0^N8E?Kg{_@M1xSrNYO|7e%zEVu=HEB(z-k7@5 z>&v`F^*ZgCAD?-z+^b^S=yTU6+UPh=l3Z&zVTJ*HvR^gXuBjW|KCh_t)*tKMrp=qV zO?yN0h_we+dmdgI^{(+(@6Y4vKmJ)YH1=cO$RNY%GVf1iF)dw=ES=pfY5wxw4%H1- zKfiSM9^L<`4AUp|w1d4r}^bjvm@_S;fk0^#`~2-T2|g z5BK~nJRaE5;*yPs{S+HrAKd z?-!k$ zOwFJAQ-+V_|8@ALy2HkaJ5QvqESzF7#c9Ubsq4p#n3v%>?ni|0?eMf4nLSFFD(4Rq z0~32D?qB~asGH`MRWtqdKJR>}s0)8!vul&-say7HhUw-)b!i=KZ@K zD)F7Ppxy*_)3}T0dQS`VnB|(*(fv;SI`y5cLfx=vhi|~J=O6Di8CfvDe#bS>A1$2q zIqCRO&5vK-T^`<5l$df3e3>C7<<6q_nv0_ zPWuBz2`RItX@1q;(cylKlelpDz5l6ypvDp*Aeo-kRFDKP5Lrd`)9NknI9Rl?99gK zfjTuds}79`{i2xm*DyZUtZr9o5dFLFgTt+ywkMtWIO}U@pNO3q8<<>1u}!j}eQsFq z;2FM;w*~I_`ltKJQE|VAp1ZbEuid4D8&;O1u1!69VbpBuR#i__s~e8kJ1@iB^Nexl zbt&0T7t9_%TYJf<=!=2+T3tPw@0;$mWb3PU{fBp84R6F-ep}*J*KBf9(*~c%P7hDL zk$F3J(0IitHN*7wit5rz?%X9~#EM}p7o5FzcItAI7z4YIGTVgbzjsZqNHB_WDA;%U zZKP8~cj=LrOH%bhvU;EBV9{~Iq;ju7&oqO6Ewt!cJ1U3otJ@vA@KI9Ytg`-h_3d=e zn<&0pg_;e+Ut(VFF+bN(XW0Ek8U=4}X7oC8v_;RN_{*boN@7=S)|&qNp+j++ZnK($ zZO5gm8GfK{czTbB*0o}T77y&d-kW?`wyDXv=L6r))SbTD;**2Zs;{3{mj%T8&0V;z znc?{(meVE|eR^5@VvFLMMW3V<&629jOX(>_QukMoS|cF!tQ6)@p`TU)Q2} zp;cMA?MUDnnv3;&RR|WmUZb;2`?WD{AFm``4OO%Ily{ihe?(drmM_@npP!J>FzHIvxA>1qGg~$>=vnY0 zP{ZuWv-ch4E$vH}ey)7=YGO>+3D;Nd{BnN_+p6`5*;A6=Uo8$hKl^a3n&D@>VQwC4 z^_g4c*~IJon*#HJo=3W^TWs3A(fzdr_w4LboF|+x+-W^S(P)i^-@4|n56)@1)JCc` z`tIrCSzE;yO#5EwJXA4qDV_DIE`F|VxYgH$R|i+j>F(osei*xJZK=rU-j2^>x}RV6 zZLt6DW|uRATNoT#tZ~QY?%1$gr!m*Z4-K#CzS4g9+lEv2)o+q-f zx$xETV!MDhqgN>{>`v#N+UMyRJ0Uk@xHG$=b9uqKmfGWv53yLLr)K!2y5TEpqMsLh zGh6Z7sjt(>o06_ZeUCmEYVKvouIqp3>NTCro8D&6+f2(SRD9O!T|8>9J`C)W=d`KKuBXZ+ zqBZ&pPkc=5GUw`2lgj%&PDy^A8awuFY-HrAyK_tThBsETOKUfZtV)(Gp!Z%zlJGnNl*!&X#O4P2k%kTI-aOM&T%;y$9= z^?S`O@{p(*exq)9;jVQfhxdFG-`K;*wldFkc1U93kc&-5k7!rF-owQu=4&j@jW%6< z^}WTFrW?8%tc)%x8s1(@v2lNmZPmJL+Y^U_CaW2Kt8Vypyz-$|vj^>MVl7{IjqTk3 zZJpEf2#aOK9b6MDT=e@Dg@*nxH!ivOE~-x|D`w`^Y*?7Ha<#tw<6h;qaXw9ZUi^JC|BcC2yUv_KD z`zyKE7e3prb8pUPlPwjmL`NH!WaOwh{9fH~>#GL-8Ed~JWttm0{+ZHam*f3eZVx+J z9bEM=ZSKQ4{R=#6o;j8FS`!|htI$jI{bXP6`Db*d)MV20eR=cE-des?zXST9ZaCsz z-A87;i(>a8z3cC6&-s{b-&vhAz@^P_$EOe2kOxDx&l^47A9*%vuZ~6f(@)9ESDz>?3O)F&1l%E zb(eE@&gRwqv`xEIraz$Y$Ikk8?H_GfmNn~j@az_)W(5|3i;rYW>HT4qVLJI#msWJI ztnzogifQ$_Em;Is~g_@c*xJQc>~&L87%#|Udw$>^+4Z@iEX12DwYi# zVQ}$aHsd*`V5M1b52GeoQY+oq=)U>+w(nlg^7}9mIK$72@G z_pPir^{y`I*a3aPFxma8Znxp(-Z_=iFX}y+YyGIL<4ViKdE@T}=PH&}zRs9b65)4t z+~wY{cDVcq?zyt(E6e^9Urkwju<+9BHskdseD2%*a86QBHN)T34I9@S)XEd@oN;8q zR&m^5>qZyrUo2^HaInGYn=_aE>`~JpNmrv^(v}vvzkZ$6wsb!s>AAXM&g9zd$FD|B z)HtsAVz2fzD*CQ&c>2zh`T6ZlG7Rf6GgGVTO?*-lT>5)R`T4C6HmvR^>2%Ja;*6&M zh7}h&ySTMIxHhGqS=8%$DWTd^EjD_c3cYuc-l$RyU9GxZy>*>?7{1Tn|M*XVsUjeM z&(E*@yAc zS(6m4d$273PF&z|!!emhInLCoob>^>L+hrc3{*X&I zEvifFem%v21r@P*?5Ueqj61j%4q;cg>b*~ov9buXI1;&~|HVI#y|!1qSa$vHT9+vM z;MjXhHFnS5*rDi9u-A#X69(F;|Jkli-Ec&Mh=C_Z&T!tZ7ku7e+MydWKg}4Y{njmL z(Bq09c`v3MKI@u%H2&C-bazF!#xWZ0zE8GWYBMJwKESBn#3hdxj=Pqq))O?7RhQOX zpT_~uH=d~Kx#`oK=+-+=ZSULQ%a?7f*3TW^E_R|_^P0D^23to?YBjfW&D(=(7TaAv zzqa3lm=7Pq4xEbHB*Qmxq9eX$MK8}FPip5T0I z*_Y#5rBySwjw&hf=okBb)l5d|-nem4nN||pO;&0aY+M<%_h7WO3r5WwHUj;}_JG9Y> zxyUfP4n_86j5Q^y|9UFmPyA>-gDKz*4Akg2U8y^SSttoZ3H(w->NjQf5T*;`rMy0bF%V~@;#-DFy&=r( zLouD1=?rK2wI`z^_FssvYE!WF`CwK5%g0Ei{s^X-n7-DC-*U3=NcCEA^~Bu&x4{{M zlh$NPSM@(#;_rn?NnbFY zC+PS;qVwNso-gR=7L=T$UzSK0bhI0gj=np*M9|TF6xpL+2eA~sgi7~7q@!=#x8`(M zg>?TyO8U0C>U+6#>p=z^0ndc!_~%;w>-IH*jt*w3OJBp@E9mGFC?8 zs&B_Lg3c7Wsg#L}elCNOW`Kfj!kZ$YDJYvE?JVfD1YL9JR9`q2Lq~y{0}X_3rQh2i zgDn6(=%~Cz(6vOmk)WgBn0IPsZfTlCeUi_k-Xf2QptOGUx8-dNh7GNu|9moN4fgL~| zfaNRN1?&d)(A?dNL;c4j2pk3I_d0$9e*h6|Gk^x5Me{|BL_I(UkN~I>hpc~K~pgqAFuwgple}Y894yc23 zzkxphEieYq0H#753wQ(e%H&*p=*tz!{s8SG3_yDa{Yt_|fPPhhetCg@zTr4<0-)cJ zxBy%M9;4b9;3RMgSPI=TU^%b?V1boD2CxcH0+~QMuo##_oi-PV1Rw^O48#I)Ks+!C zpr7rcpYpm5Q~?)&i@-U6e%|FWa0R#uTm!BHH-MXf8|n`NIs%=5&OjHSE6@$-PQSJy zMZyv20h~f5OK@wT4bT#>0Gb2lKns9=3g!*)7I+76zexj5rVa+C11UgXpdZi%umV~G z+CV)(2cS0(bd`2_Oc#YQk|uyD;EA|sZ>1l4pdWqsgeKnrZ-I9}6_5tZ1BO!{0OtWZ zf9brXv(yFd^aGY7y#io?Re%!61hRp3zy@F=gQGYXi5<#Qd5p1QACmilL%Bt-fj0gOHkuYM6ie||F2KhTc4_Ds6IG_detpE$4Eif9o z?qK>+CtILBK&N0G8m6D=priQ*+UI`a?Fo4ZMKuc508ywM1GFQcQTmk&4L}p1A0sIP zjsl6m6d(ym2Bre^8$i=guQ%Wfn4(TSfWDVE67n#>1E8Pm(4$S6jz=;^zu7^*-%&!p zV}&c!m_Hs{(8ocRLy-a|%QW?pfM{T_=#YIr^OEszg96=wZa`PSADPq~9es2J(s6hO zcnLfLih+&5A6$dM>j65w=p>^PjZQXdbQwUW&?4pJ-Hd@^7Lqf7Bp?w;0O$~+Lx~Qj zNFV~31WW`b0ONsifFCdh@CAGT8d91Y9ss#93_Jp$c~0wMC@=&dJ;`7N_ZyF+A<(8q zn;RJn0Q`YKAP5KsLIAqw4+X-3C?FP?43M*wjt59jm;y`(rUA(SZJsH>OaQY)lnPi= zYAzBK-5g*xFb|-kdNHsRAg|{G)Kk<$)GJv4^*{!|0u=cQU?rdgRsop+)yoE`9+lYu z(BP1K5;)NcFGavHU^B1@psu5f3LaHj>}BMNEl6$!wgat!Z9p4 z6b&^>4pEeX=UiiyzaF4y)%7nRKLZ{M{1p5Icn;J6G=->lUZefL-jSy&uW3`Hr9T1~ z4$xNM2@C~>0A$D=JP@Ga9{{Fv;~mmum=1Rf;60N4!LEQ7K>A-GI)CUKQn=vd4;0^k zuRtHjGN2_;2l)#?dlT(ZpOL1!1uNha(&R)3qa@73cy`1f7BQ%DMuaIBk$@4A9~D0d>^__yL)GBcDD3Ty%P%AOFLf^%3^kSZ~7L=#XMB%P{L$e79?>BJ~UJ>3XQ2I+Kf z2WP+3)vsAMWjb4T6bpO}B# zgrHeUjZsA!N7ZB`9W8lPrGHQNM;Qa>r+AgRLv3frBh8uLW@5uoL?Gb~3e~(DL z(qQD1L+WK{(<5W#FfB9McmBQ31n8-u@c=bO8!heHbc6T^>_?wFC&RRjj{!ykw9AeK z$f+@a4M2CEwByn(tPe06pgT=Ut2auueSOwN!a!9Wl|4g~@M06C=U_W!Oy>Kz($njbX5X(Oh&MH8IPt7kO+bZCV_5dusE z!T@!Hd#C&0yTBdbHc$ne0d@k1fjl8i^{L(= z$do<^-Vf{nb^(-E0L}+yA}#0@qTNtXfqlSUU?wsT2g#B3AhMc25tcVS7qM;A4S#mof&!$q;7UY2oY3ZH@(n_A|So@;*xBVg-s!wP(n!n z5fv0dJM=CDPy|sBLAr_{LZ~7gkzPfq3g7?SJG+}e!ux#wo?*%<_uSL(o!LD3IF8^r zf#W!iV>noVom>oe;5dup42}yp&f~a*;}0Bv;kb_D3Jx|uKl>BMW$ped&ew3zw_66Yj1i-z)U}4V&$JO0&)k`{u7)2L=fFFZC zWxO@O^9A0?rw@NDf9sD4F1+0oyd4Oqf#3&(Rv);vuD7!1bQi({ircP)>)lI8Lu~tO z!w;>#__~!RD8`h25CnO1ifTcs~HII`HOhU32!><8M?K z-X9bQ1XU?Z+jhZ$YLs$?7;3$Yoc0#T7CL-BYTCq**W1i;UcS)5nM zD<_1XULWvOb(Fqa@x#x6KjA5SLFe*u`j>X{-M@5-8I7wdUTWo(@f_ee^4eQpB7{N^ zLI{PURAmoB$j5`4?@{U~H7Ruu-1{-*?!h!mc+%&46koNJ7ySrm`RTCf8<_5VH+r&1 z2~&=c@dpgjju!l+)Gc=hb~OR|!tECN(ut%)Y<2){kOTK+cXdFHQ|ONl-VUte;_@d z#4H>G{}1xMFzcn1@8sf|?#nS)1rEz9eSuuw)^yA_!)~(eG2L@lg}qX|RbREXf$r{Ad?V|_**Q*2|M{0kw-TO11z9+Id^pm4TuQxqCOf2sSuzx;aLuVa6ci!=z3K+xR%d9Bg2X6eJA z2{%Qu-KNp~pW!ggfxt|c;LM?E-`Z`>TD8hOAR<9=3~#5{eX!ea)CVWSP2lkahjrV& zdS;n}p9>*SP_Ue;PE+?OzER*JN}FZOwPESKF02$ohBHQGfVhfzm+Ix;-hVs_M}1RR zeICQNI-Lb=WF25s0!HtNfm!vxdGLea9vX!?MgxNXWEU)_52PT6xB0<<%f2JBbb;SUphu^$z6I$Z!d62enHZ6L2-ltkr68vP6IjuHI= zls{=dpYGCgoD5IF82w~UcfVHo0rxgQYRAYm0;v4~gmJw9x?4{5HADu;TI}V%eoNMr zl-U9^g6G#ffaV`SV1G;p51{DG03uh=9hI-FYPlhvmCcnGC4B+;|B9hkQm0=bk3iSy z7XY307BkB@BXD;$NH8V@H6DC51cHgV3Fx)`cB%PiZ@nS-i5h>4nw|lpr*wm>p>UL0RXPAS@ajz1*vdNCi69%FdBZrCXU8go}JE*i(j#Gt0z|W_hhZIk$ ze@F??w?%C63}(-0w&&=#J$*r>^#B{leh7Yb1_)d;>~Bg$^RB=jTF%z}1H=e43y@u~ z)41yB??Xy^{n9#e9?iVY&2F&0EQ!V{5OkxFhvC;h1xwF6T;cM- zk<*PkK>#h(2L=3k9U}@i3McO)N|G8IN;yZAFo)%l)D9ehVh53O6p9)~dYts7(D1O1 zZd9zHt1zxsn2kXg!J=p}7xyann(yZmE`V13h&*t@zUOqe>}*9xlw*56{@* zneYAjV<8zLF5InF6xBF}s0jfEw_9G7-|ZcjbfN(;B4pBO1PI4d;JyzQoJ%~}2M|r6 z?^5zHq@(UY;B>S&f9r}w6&QpPL#D6KY^Uukq#2dTlcPTiJzIJBD@~hY)Rn!9z`%@>l(s%R`&HFzgznX^Tc1*SoJh z<$}zmN+%%19w5{O>7F&cVn&}jB$^i~{~d}2g6dVD`kp{j(imESH|LD%-aO*hs2M3P zP6;#xh>??lz{xhc!;NYk^U7{=A$$P{*1b79rsaIU(=*WSf*hhVAXR#j-b>Xfo2cq< zxRdRNvYX!d4PMojM*OBEs?p8q;%`crq5M12t-8H;^ZAph<_E&s=sMsf?@-7|B}{)r z!`Ra+A8>avRJuQdPX)>R~l#5SPS84r82q1F47o9(;lr->PZ%5M4$=$Sjx3y;I zhpH08%_~C5zs0QI5lM|&&>@_u^?9XQP+wqI1@<(!3VW?%#p13c1Hx;kjg|Jk(66ObLTJW3+Y%QXn1Y4Is6R^(I*52H^2 z8C$5@kv$g&^a)U1bD2ni5XYkkv3bBi@JR*S`Z9z5nl_4{)Fxa!WCOC42gJFuc0_KE zi$fj?Q;K3LwWCa<3P|7jBJw+jF-)N4_d9gs0`Q){D@*V8dh>p7*v+@KFuYjN{Ym*i zpcQ8nKebA0+I0qQQiLM`m$~kyDrdp2TpMYzl0Cz^9w@zUY>IWDnt0a*2+#wxa4weixv+ z#+}Ih23)=U1*Kj&-_9uPm~THCe*s0kN^8mDM)IC{J+E}y4;F|y6PABc`B2mUVWf{K z;3CvH1Ak%+xnL=+;nQYHz6d#gq9qJFMqBuFm5$+I)cr0py>~s8^+sJoOag}FC_n+> zSBsqZC)LgOTOQegYY!)O*g~r|QI&vE4Hz48*Q=GL{)*jjQ5uj80|4RLIdWNt*5mxf-ts?9fu|B~SZa0uzn64nk>!?Y zkl)Ae^!5Jr9ybX*HMs4!lzRg#_tDPlI31y?f8unJ>f&Vh8&o{0Tk)5!J@{~BTR|oI zkP_YLy+4u0{JK+r*j9bLJDt6Ub4Yhu%gw_c$nD)_+w41G@0@KtSN-AA!!ba(OYl58 z`R#2h_TLf^(W#uJc2~gRCSCYT@hmMKJs|T<4A_I7Jr>&X=s|s-LB{Gm=+G6^hPplE zFhSWZ-n@Ua2KPsyX!oJk*dA2QmsX~0m5@)ZOI_H|Ob0Ce5THk^KHCLBhOM+D|}OYfClGSaHl{(;z^q!fz^E zYgSk?=0V!bioDaND^nnCxvNAgB4B+=gu7{qnMwB^AWYF>Ygw*P zmMl@<&6%m#1vvr1bY7*R(aOpkr036WI7#nqzWv~)DNWkDlqBU>W%BakF*GUr(*S57 zG8L5EZ5djO9eVXQHNd6#(SY~>VwxTJ=JnW`*enob8djb`U;KqqBN9%yw4gUze-#Da zhkP;$z3ILCN>rp0C#&7!eTnY3zAHHZT(~8HKUdHo&-zX2apQ1{6B-0TDAmdHJfx^> z0_1#$`{-ZS@?ct8W$&|%!k-DT#A-=Stbj2!GGS?L*4Q8Y+sVrjHCiHYQWmT=Iz<{o zeJ6ny|AX|drR~#n>mM)@5g-D}kz%#kmxwA{xv0rQCBjh@w2Uq6uEU>Atnd)55_M}_Az>#mV@X$ zG(3(>S4tEcU;Xq!GG2!soVuiKhoeVa_E}D$PcWOWsSTf;vBpK%>C=(;v_dk%wu3YX7s`alyW&KoRPt{cJOnh7w6nK5S(Dik(xip z+zNS?6GOUq(WxPXyT&5(s%T*~^*BOTMxB;1g+ppa5M7UVNs);~m==8gqQ}b?Nky;N zp7TLrgUD90M5LA^9h1<1ggn8GT=Lwlfw|^wyI>Twaopl)lh)cW!R@8ql-oj4@4M3? zRnsg|?LJ>Ee{Egua#-|#!(?_zZ%n=LPXvEvUUZnNw9 zq)qSJWyEcPB*+WdNXs>jv5T4LR%(dNX1ZGfb*Lb@Xp0L*vsWoz%*+0k^}=ndzW8dX zwF<8sRi8Ul=JQQafqO3foz+~MiKq&^9EqF<1l|TH->C7}FO{jQ1cKPJEJjl(1!4)) zPG8K%HJZn7$)mK&i?EhBIX1%d#V<|v)0d^OAMbCceWlefWfuL1TjeDAlu_#{SLp3B zYD1+jWtYK23(YA5)?(H#xk0;EE7+%CZ5FGDt#qR-n98M@8j(dcu;Hs;3h&`kdMmb1 z&&Up*^)(*}#}^mWrJNeBkI#}_m{<1$f7A=w(_d2>!XuxS0KxF`3T_xk+`-eaPMe5( zF60mM2DW|wWI&E)7$~oJH9A-p5srA1|GzDkbrRbzg9z1*1<}QWgXkrw7ADEI7G! ztK4kXb^XXLJ%xl~Euw8AI8^k?1*W#PG+cvZ%q`ZWCfO1n8pLd{spneQX(g$!`d`si zNt?W)#uXho+}>)Imielf2y?8c963)zmM_%s=fW#gRdpS@W=k8T&OTUW%!h3Wv%*WK z2u$nc4+P!=E~naLoH_rG4l@Pp@+eUgH1J_=JV%keDq`+s(!?A|nn61;DqaMg;?qp} z2k6=>KK+ZLxGT>=#S8Ox)UO)wo>4BJCQ^Vmh^NrGYN}4ZXuRwYY;WE#SGV@Rm;g3L zO`(YIb>!gzbomWH0PLi`FY{+)qe$PkuTEW7(uo@IK8^A~HfanDYzXEgeb#+rZpRgA zE{s0(r3X@Q77%IxA$Rlitq1Ha%eWBcYLE|abY1cFinh&MkniX}km^qXfj2(qRO+tV z+3J2Z7s9_3idWrg^$FD06GFsJkUP~sHyr(5Nan;YF2s1s_QZ?|ZBgrYH;@tYwtI4; z^H<07G6TCzyw%*66yDL68{4Yy#}wtIUe(kWb!d__{FrYu?R#U6VS5V6Ml7>WYmiwR z+bk;^y@$6%5i|frvcu7?GyX7;FGNq9tsbF0=(F{h-M* z-Ho{S&2OigZXF{z!M)rL(;ddUhCc|e`uInGi~6{#W$?3z19LDI@4G+^!U1%Vxl?4Ni0)-gxVOY);uipN!g+xBfi$~h z=b%TqALhFtyQq>c=5YoHTzJQPwC&QRZy$|tAv~d2AgCTwsjn{-6FgPAht7DoOWt?$ zzH=dVpeaC1umORk9G(CC$gm*?lUxWB0paZ(OVY{fA6+_o)CI{fr(-t}Uq3aNlhE(2 z{OOn(UTCw6r>0p_7}DVR2i|K+oxxr@w!)EMDd(u-BVJvF4YpY_nY>5iag+OPZnF<4 z93SX3(`;6I1|aJUnY)@Lebx#Pem@sNX}c94e>SF@<0ss!9%b47`LqqMFr&C!QOFK_ z;vU7tX?U_NY>*wF-8HvF{y1mW>p%%HMR6`jOHWNs%dop`_6WVyd%*e;fP~48%yvq=H0U#y~GPh&?U%h{wRbExH z!H#L%9KSxV@hH;$Xp=?j#6ZH6V?pEvo%Y_DySESZ+d&fhey zr{!^3u;EHCO*2?5sv82O0!G@_*}7YOc4Vq11C-hV5PowLJD_RHl56Ji=87Q3XH0&LCHM&n=eLeP$KHF^7-Z6#&O7BVFOh12P#yX8DQN=*E~#Wq!g zbUSkMwX9V4UVub1G4`A574f=PK>mM3$(FL<(?Cti-pc$_EgmjH!FRmRC`Nu*J-Oe=H!F_Rz6fT3L6+cf6{n~arZXqCS#h1lK+H|UL=4`3LKf;<-XmGqGhZ8R; zK~y}u^>bwHUp}$PiJ0Sy@WPM1G78bN8xXFA8&~NnzIW!oOjY3^iU#B`AfA9sF-`ls zciWSm-jWEtp4(-BJOJ9=ct@+bYM;R_q8EJ3>0_)?xV7Gu%h9uXM!P0Z+=zk;h@qF_9aa1-2nbbY?%Ju#vuq<^!C&McS*;7*ztTs!V z-Fe4i&JR5cE+RMKcYItBytBdP3yj3k^6!b(>N{P3#d}kSsW$_{txTQhQ>DKy^OQ3i zzrlDV&8MGA3&gbEcROsJ-|&($mBI_&g1Ol?j&Ds>yr?gUXGLZPYl@XYZa)z*@=4r!fG%!-l=w-(j)N&y)q^bBWNWAVvX^v+(^N#-W$rDq%#ezn# zvo4XsjhLVC;p8#zekRl%!xd&2Ae^9kbe`|_?9LM|*4)EDxEw&ZB0Q`%w)~-{?y+1f z@X0Sv$>r|FheYv#P(**gDHJO1xJ(YY^hQ?QjK24!4#ghT%f@Qb7!?NL=aSPY<`|TA z4lKvteM#u?$!))0?cd=ENXVMy@{QT*ipQTlfBRU3n6-$-7N5yJQ&+8;t!q$~qk!0@REBJKes`C59sdnew2Qs&7~PXam5Lqj-XQ zUz_cHWj+YC!jPwpc6ZO{wD~7~5gV^UcEK|H^%b%Vb^mr!>4tSz90Vc1q=$P<2E-dI ztEG1S=I{sAh6BPehY7r~QbyLo)zfZxdmWwt2q!@VRuOfT4GqW#pm7JcM^8p3?dmxL zj+2TfK3I+A6j437I$)qRK;ck`@3ni^!YYePsmd%o;jXBNqK~eR#XbNG4*SsZ_iof_ z;aO5uHsc8wvm)AosBB48q})P;;)N@s9ng|^@iZ^B1FtFl@ev~{=$AY>*IqwHb-v#E z@l7ZdD&-l8FAsIuMN~4)9#*??m>ns#=H1UuHJ@?vB1pCNQv6{eOnvy(WitYv-47QK zkxSaGmiEZn?s?v`s@G!y5q2+Pjyv z8>AXGJqlLsCF#^4RZkCtRsE)f2zC1i?}65oU?+|9f_8?hhHU38vkx}%zee%n4^u{B zR$OXwvN4S}_nHg!Bc+0?IL`r5wAY^DVy+D^?j(Lnp5mnyEPG#V?l>P?0T4yZcRxv4ztE%(zs3&sF^94 zZM-#M#ISU;e3FzwaU)efZ^u0bcEXI&v}A_*dZ5Gv#Kp)ck_3yx5>UJ5YM_pUOxDdW`AQBoj{l|w!w_ebH(3^^bn(`FM=8{<;rEylD|Yf1)%PE@O# zih_VsaWuKh)mpUw6YRXk@HB#aoRbHU^V%4}z}IR!700qUZy*^LE}kalZnlYu>gBa- zssbc$DfS(;RzLxZFgurhj2w*7w6mpJTjv_(g&~Es*~NGw%Pu_D<(z_PsBn#fu4GP6 zH;*X9CBfmxl0#wN*0^TiR|LsP85wk}jq3YGA#zL;FqZ)07K5WtrNGzJz*Ys~N#pHA z*6!iO$cJGs$(lNFkR>hy3@ygNRvQI&RSi1FoefN!*G80KTH>$P3@kWRN$wm>-qO8x zYVELsXbE;F3YIrQa3P5dD94J2LYpFiYfsI|NteI$U6OJfu%EfGfNTdHMz95NLY z51j)YN>Br13K>m!0goY^M%)P_MDffyueuTu%!6)qL zg>jPMGg$ob3_yeQIw0P{Nh+O{YmB#~rCZ|685UYUOf^(5^a!j9VK?HC+)LOHG*Bd^tja&%5Y7TCcAV@EQ$CNppi0u*_V*>HgE3{q=UK*xvY_}3UQ z>jO1N=WH(U)Oij4X~u9iApkqCjiIwzs;_w!7097f<0JL8T1c6^dkZ4%3LLpmvO_>E zmUK~hhgemEuk#kjE|@Wta+B1cs$zi&h&V%=AvCrX+yL{!zA>K53rsMIGi#t9?u9;I z(qO14 zxXkqQ5n|L@u6r?*6Uq@I1JIV9Nu!%0u5e+DGsh)afa$!39G$Y%*J#l|)w`Np=(0|l zS?y+XoIRK>H&E-8(e9{9Lp8`xED#Ff3^d!!DRGu~BlYXA)^H-PZm0$-5n`70obqF6 z=QUy%^|T--bfB45*%@o&izgrwXQNo+GC@jOas!!gWKY&i?ot(eWQ-eRcc950u$kQ9 zV&f#U2Lo5^i!+?RD=(9UJ5zmF3GqByUOF`}sHTnqtq-Jk*GnpbRRsg*>LM~m(( z#k!rR%Qf0;x0+M9wnw~5~=fq+jimlpEqa^q9u23ykOEeX~Xi_vDz8ev3YNE3ND z=*6d?fANuMO?6H_z;IqWbCm}kVG%B2xDWuY4IX#}hNt)!k<3jTR7$DKRQxz@x$4KM zj9M>OJxlTCP!xBA0W@=&S`E(2tv6KztOQpBPA~$~-5CR7ahDuVJC>;?sy0@A%|kBG PF`T^MIHZ8J>camAHK0!Z!cCf~S#Q-`u7b-XJ;*6J*~6!jZi9j76LMyV>MEjmF#4;u+>G?1--`@F zPPgSyWFUGfvNkdvSreHzI(y0_>ZxDB<+?Q=!jD8fc6-?ysE z#s7k-dE;`2;dvAIO6YkxDtS=}SQlBR65H%k>-cDt1GtjKL`wm!C6z z0yeI`5R%c}mT5={x)C`!lOl8TT;EVoJpUXid=I=5{W+wR+k})M{+N8}`E$sI$lSch zQR5~Kb2X@M**yU-Q;<7q!o(>#boLRO7@0d^MC7zdu0ahfgI>lezrg%n)FI}}u@Zkz zp-Xck$7N5Qnlr*RHfQE^aeqZ4%Wf`q;#N-Hw5hq{@?1H2lc(iOndw^G*eaKkH)%=^ z&5ps2IV|vNVs$5b_{1qP4nyF@{bo%~zF9#x2$`u7Im4#OV2;m|=3Fixyf~KC%xZAE zk>TGs|N97Q6h>#~MdIS9FVV#+dyMI&>$m3CO8E*YwN9Io8##s=Crz6$bNIv&Ig!*V z(^U^Cfn3>c{acnD$%0cz;peruG5;_;<$?=#lMw&kM9MfZX6Cs08@IRv^DjqRxlyr} z3`a_JFC%4AWu~$jHK&%1)FM%MA5z@)Z)rv8baaW*0Z4Jn8O`H_GI3B4kYH^DE~M1I zs1-X6YrUXqYpa4;NSV$FNO9LM*&byK36z44NU10c$;=j9N^-fFk%D)T%vix@r0}zm z3~@mQl8Gp2hGeD-{A^jAXxZ&XHejt6C=z&5Fa=2{74$GOTm?;#QlW;F3Qpj;zWPHevK}!90H}eaEAp#-% zk+qRu_OT3C_q7K8vrOv{(Pn_d<1qCC1Q?{kQm-ZhEy;Sy*hFp4#)_^-foRwdGf_`16|fiE$nI|Hx9Oz z{1F_L@?(ZrOW-%8l>dkh3C~73C1+I5jOElL{CV=LG0FwUNytI)h+SYPQbO)kDilY$ zXWNGgr091HvnKX6qzwJ8VMa4go&1f|D{2~aEo4ii3~VE$%uFq$d92N`^q25o@_$E{ z{w$)ulK(dKNb?UOrQ8YmrA1UtSAcJM?r<0HYNAj-)Ad5|)2m&q6YG_p4Gjd7MvoJDeY$U6!jATv4LHW-PN ziU%Sk;@csmqO0^+M(8VKZRAr(;R}({lYz)u$WFG5M@l`vGBOhTr;*aZH<(ee--(ot z1x$gLwg03|dK`;aoUoLHC#qcrkmNY^1{h?gU4Am`b9_KciJj#D|4Y&{DpBiJ4( zj(?9AQs2tyRz0D2S{HWB!A8< ztAXK2S)I=6?v5^Y<{2ty2S3cVI&=mpBl^x9Yl>2^lMaO-sV~1^{akBk{2*kg&d;>u zAXcgrjANLj2Mv%i^p%jpcQ3G}Bqwip_PFd3IX-k5!3d<-Gri`BvDukZ;zLR~&e^8U zoRs5giC#%!;Dx)ajCv4KaMS|Jp^$~v{(t#y>i~4rmOGHQVs}4Mw&}^X%s^5{L5wY{ zBkQ7HqP{xFk8HUcDI+^Io4GcMD@WuXUSf@He{v*j|Fz6s4$G}EUXKicUxJj$t!T>) zD=fQp*h|z+&7PD!Y+R13)4f*L4fx9Ni)@*)(i+HYK&fI6Y{x*_LwcuH# zOzs0nadjqA3JgUCBfBESkukYbS)ndhXB-y?b}X)zr0+OTy;>zrQyEh=pYfl16*SE# zs^@ce(p;`MqqttOHp9pY_i6i#!f>BE+t207G}eYEyLXkOs`|TJ{Y-8IsXk_EXGyA} z+vUnIxk03QnyDvArAbQv$!#33pW^An<;y4>(u`B}5F25$$D5^S4GnLEPs=m1B7C0L2)7iv;xbOPNivGyyBOZ)K5c=K#rrEp zVRN4+mSt+$WVcT8M~Rn_)*d&qB7ORgHH+EKUT2;dv?bC9MtZ1L-E#j<~ zSth%AqE_1|iuSpWu}|D#6h|g|l890cuHt{aMC~b~FvjO@N`wwE)(&gqPoldK7M-l0 z3^vxprf4BXQLIlJXn5m%?v=!Iy4jKXMq!*!a~nmxPcXbKeV!Ltw&JJD*w7+TKU3FO z(=x?M9v&`6Se{g$TdmYt;WE_6i-xr%i0_&&log&nrLeaT1)KR z#)j}j&j~xPoUtJx(G%Rj^2%*w_e=DQM3ZvgJ8{3OTo5t@vMpTWd=LK|E4>qd5B zqPw?f#^9)A?S8}C#^-qtJs#VNhKye2CaeRig~&wDKr|Ukv$y)rCPr}E6wlX?5?vL{ z0&z_(6MrMTO`>N!nnaA%>Ah&NR%I;`J>Q`@D7Qda)kPAW+@4$ZfLDcExc`_z98CI z6PM!I1u4T#JX0Vr#+n~%F!Io39r%mm?yYD^#@c4do>Qbs%d}<=M6*W#O$OI=&GROj zwOXX>*U=Kmt0DuhB^cf=KF`EBRx+H+V0+N_#2IV4qT_?UgCmW#-C2IAKF?Cxwnr^B(ftluk}f;BL~Ocj!vYNi6(vXISGJ}E2r0;w4$m&2S- zH&dUGnq;QZI4iMIdq`O&nlesf%v^)iNHcYX)HpMhgR55VVNzC!wv44!%QjM`omSt- z>gn^0?Pv|O71P_$q^C?(*F?`XG+EY~InbSrtX@9tZlkc5&+|6nAQM8oB_wM8hBw2< z4xW|abH}nkx*CHslHD(o8e*pI=;Cq>G*d5;8epcXQ*mcAHGouKNqL?oB_TkBH%O|0 zB29RV>|TkUMybxyqe1sLwARMj*2$j#kh1rh4vFr{%yw%dtV6P=GbvdROmJeN=WaBy zDsL{%_s}Gq*)ItD5F@Lf&(kl>vM1iy1eT!5`g5C0^xtVlaQ_s|Yh?BJdBzZB;v-|y zF440aO?Ct{Hni(#Op@#mxSW#iauJmh!W~(!XtD^*^9}upbYoyfiu)>LFLMymiAHlc zJO(N0H^ta;4xvdu%1f1;Cwa4ca(0yE^Az=D>P2Yn~z(| zV1nfsr*$-n$eo5hkX(BIXp(<#`}D~(l9V)SE#UQNvYhA_`^h)Gjo{l;+ztB5G_rEF zjYi?^KK){*aU42rfVj>o@T?*w!)BdR9Y$kUkUAKt>u7ejnkBkBWl0%1J})C>@v<|& ziDr#1Q{xWB&;C-gHpTD`^?9B|x9cC6=sAlflS*ssWeo;78-~;~63vS>*|Bi{OscD8dxy16nSOW{qgewYuD^y>GNf)fE9G1- zD_I+66piq?51P8vJ877+6}?NUjZxe**;8S-H9*!u=r%MfDr7~>xIudwO@h-5T>bQL z<9K9>r_Knv<#gFCAB^-j-F=IcEFWu@E9N+RtQ_}-p;>tnl$(vL(LPT#qLhe~IABii zK#gn8`q(=ShNMm4biYIauO;{B(p$pKgaggxYTaqW<*BI?gsGKVdMY~;QRNph& zI6fvtt6_M@`aC0ZOR8faUqh%~W&KP8OK3Xp`f)A5o-@#Ye8Ul^NaX!!OV@sxo zqxWVs+by)u(5z{eBYUH9C6=5!OhRMD$T+o5^c=PGtjVZ5-a6cy?RYZL#5WFo(VR@7 z+2t693uxjIQ$ttdC*Zf~=`>Q(G8-Iw%^tKiR$EOI-M^sqHNqOUsW8!TPZq^0G+74L z2}CiPHNCRbT1=7{mP6TmQszO;bC49FEghiJ#*^*!;mIdu)w6*;_Z_qjM)8p{;coOxE$8qEss@QGXs-Rbij zf=QxQCTDDtku}5T88y>7Pc;{Z_J~mgw`Z2)o-B%6jI5bHZJbd!)92Yb+vVcuUr^Q9 z&@@rMKHCVMmEvhN$B716QM1wP9g)MqhiDRg)=5&8xsF4p@;GHiW60Qio+Bj_#ER>m z=)Q)=1t;5C|9O%Mi%-^HnP&`~lj7-;Z%qr;vBngdgp|uX>J{f3!E;kQ!wQ^el=I~;hpExmK#~~I5C)SIbTCgxjdEcvfQm%{=1Ad`6+IF0X;H`W0T!8NwqUm?H9_qm2g{04Kq`f?r~D%NpYn|?jce` z%v8i8C$)ss?I!nSNvi!~IgCk(heEZTDZ{X-m~0X zk}=7ifECuhLhy32m}z9)<8!|V$5EHv^Y<0T@q1D{z3wg9!y{N?Xg=I!)ublsZ{BOH zS(KucH;NYd-2GR&TvOsj@I59yLK&%eYg7O*L0bmq`7otZ6L3*Qqs*$ z_Ww=>Q3p%be3g;CZzle>u3^bO;u6`}mR*o8W5tHXw@9KJklZwp#-R;?tSWQ66CvB4 z`4TCe?*+s<1ISCH@O^;reSy5nO5yvNtR=-h)7HyMKe&PBEiNlznn09Dsfe>7^CeO< z<~|mfFD%=>ONwU$seU+cgVDC+pd=rsyQTxlp8>?4{mgWmZOY`?a7#wBP?FG6qct?wHvEC-guRjV*8HCT2ealo~?_N{KK{`59zu0xn;{TDf+W$ib#HV_;r{T7PWu^3_zRio2WCPx0Nkk*dBNLI8 zkR6aR>z$GOcXg3Bk$p)>MX7d1H(RFJvO7}NuEdVm^+C!@q$H)h$bPo$FN%@AGcaGQ zv(Q8ivU6{jTwZ0RP=jq=q}UHdmP6*)a;%+SR?5UqfEPL0&MzZ_O}D0yA$fVWfk@4` z`b1!=bZWNEimZmd3@O#!i%dYifs|2v2g!feyYhCEB#O5L=1Zyf$ra~6w5^ZY)*^-f z7%9TnVT=+rPJq;V)6}dQtx-R{=JaAM2h_-r1av7M6nqD4k7ZoZQw_%(y-f> z{`MK_9H3d|SxUqIu(Yp0#N2Pw6`ETk=^)IK}^4O?JycT~QlCNC0l&(d`cDH#{+ivCE7W8d2OBBk3Gk;4CA%O8=_v0st$5?KQo zfU!_jkrH{KezY$Y)a6a&t@3u0l#1)yyhw4dF;Z4?B2p%*Em9_~gROT(iUU27@+vE( zd>?qp>Tj-SN!W&eB*lSD@;6W$Plvfg#8$$Z&*mfF#V8LSep;*eYh$g8ZB^xwKPkof;A!#}w;koNxQn&E%sWu!U*k)45gPT7UT zKi3B4g8b*&fKZU&_~+Wd+M&x{6UZ_VT~_@+*9LNpAS3b5wSl>Fl({BgP|RzEf36K& z|6Ci~!5Wce^3Sz_xh(#_G%eN0I-Y3^I38f^MN2b0pJW;pJ_#_!f0Cx98@thVp@p4D(|Q`YCo+xECjyK^ zXc>mLIMWC!4lw2xr)hnRgJ=iPB0o*j`WZ7n%`|3w8ekNoWg1OSW*SXS1{f<&rfFG5 z5!x}dwx6YGgN!AgWg3e=3otIA4K`Yx$~2Nr1sH2jrD;Qrb7*JL(od&p*+${%Ok?%w z0OKm!a3l5eOrz`P0min^)A;h@6|~D}gTF}QD@a?v$TYTmLI1u?({hchFX`Wx^bc*U z;WgziAL^M^zSSBhc?;po~3_h>EGEjzQB19?EqTjxioE> zG4mY#J4gS}?lhWyP5-{8e_y9*GmRp&V`y#9r|~V4CFkkidHRPo*J$+({riUgeUru) zIM1P-MN7Ysrp-4BFVMdW^bc);k@_wD`hinc8;amaCcCzl>bopE36H z{;c7>Mz5~Xt7~c64&&gpOuix;emzrr!I*iSK3%6z*VD9}4>r{@wOtR+=Y98sMZCYH zVl>1awFIFSYg&N1plNB^Ue!v6NYWwJ>JYE0b3&XIBHa&Szbf>DSnUUKRfyMBsy{?m ze~4}V5O1n0LR=PNup8pvYO5P!iyI=K9K=DDRSsf6If%VNyrVqjAu5!I7+)UZklHQ8 zE+N7yKpa-N6(B}efH)+?5#{wj1bHCldLTYf2ZcBwL}W#XqiSYFh#3_jiiP-CHLV2E zq!Pr6N)Sb=NQh%Xw5<&BiCR(_VsT}N3qlmDRsj%60T62gAWo`tLYx&My$ZxBRagaL zbrp!KLVT`Lt3q_G3bCy!#Fy%d5SN7*Tn*wYwY3_=mTC|Iw?LdzS+_t8xCLUb5a*Sr zIz)x)5aX*uTu{4(*d;_*4T$elZViahH6RWNaZ!0|LIl->m|GL#M|Dt$142aBg7`_z ztOYTn7DTZSzo@3QA)3^NSWz3|iYgM~m=J9PA%0U!0wER$LR=8ys%jMkkrV{6Hb_g; zue;ROL0W%xR+w}zCYmbrVzSx`aa9OEl^P7uH5g)BFoauO5#q8CgF_(7tF0jrTS6cL zLLodVD->ctD8ybNDk)DGM1?Sj@nH}FYPS%(gb1qxQB~#Eff!u};*b!xC~sYept=xq z>q68}2ZcBwL}WdPT54uJh#B=DiiHSNO~WCYghQ+dhw!Q*A&v>r_Ev}xwd7Wa#kWFS z5F$*qst=J=A7X8Nh`Q>W5NCx*ZvYXl3L8MIZUAvri25qEAw<`P5Zf9;G*nlFxGcor zMi7nF);2<2%4QK1RM_$CmMYPS%(ga~U2(L&|2=+)?^ z5Ql__QQl?{LCqlMHiL*$2ZcBwL}Ua+yqXySF(U$^ScpW`v^hkR<`65IL$p#wLL3vK zZ6rjpS`rDdI1=K55N%bfD2Svet%`ayO6#Wi)Hxx}Mq!cO0*m&lum!~G77$m3=%`Yo zA-YCGY>S5Iq^<~YS%|?g5M9*P7>F$~5CO3esVXZLVn8g!ULn$yCk~=Q9K`rIh;+4E zh+RU2wS?%Ya$7=-ZV7Qnhz#Y8hX{&?m>Un#M;#R6fDn-h5dGB51c(_45XC}dD!%i} zcqBrsNQB5zMM4}CqHPkyAhje3VsR401tA8jR;?hCT0yLB1u;~e6XL88>8&BMRbgw0 z)vY0}3Nc)zCPQ>hhS-)2k)y5%aaoALZ6HRet!*H-w1Eg{3z4g`+CmIy3$a&-vC5MI zQ6U9ld%@^HrgUyVOMy3sh=nigxWx(QTb6x=>w#(C$$gT@Z`ZRuPMp))lctWr<(2sjlAUEP@uV5*pb!Uyi0lsW zpqkkoVn%m}Vj+}jnhwz<9b!c~M4>7Y;#fMdpVEWae^@Q)0kOCT78iP8u~xO}iGPo( zRU+1@b0XHO_Pr1rRH2B+)I|{+RcZ!eliDESadkz+W|h$!@r2rnP+NLaZ9pH^PoJq; zFa8|LUoD@kRdDa%v^`+w{ApP3eC7eIohDfx=ivpnsO;s`aK}`>cO|^@Oo7Qe=T6hQ zXsY0T&0n3Et_^V44!fbAl~z5@pRR9Lk+ZZ{1L7mBzs>U@t67ao-6PDp$K*xM%pO0^ zm9umI94*k#e{p3`BfF7v!Q?)KT=M-v9Gb$diAC*yIvs(0h~h)6(WPBgJ6`)pZrfYpZQ1cjWFg?b^CrPuis1 z%xXy3CV|O7z72IdkXui3 z*T@TkK?snUtMgz(y>7bPw34spdI0_Df|A4o^e()N273=`dgBQSyU>A^^ zw!eYj!Bub#Tn8HM=)fPi{fO0aB+7#dzym6RN(67!V8MfW&b_&Td979bkL zfLIU*T7r0x0A8V>+)1wo!a*pg1H2#@gaEn4|1~%dz5()GgKG@rbRdg=CYS|$8iCu6 zgxqGh71RTMz#q6(Q-8fneh)e!w~OT#axdu+cmv2cUQUBAfPB}Z9gy$Z+y_ z$U}Hi!8{;eF<68g1xABhkbzAm7y#sJpKk*BI?g)s)`K+A6E($>Vtl8ama?CF=zq;DgO)j9Gn8j;l^S&9y9=vaPkF<8Kjdy zYmf};2J_Y%^Z@dGuApta@tU;9+=9bvuSA&xHw#E6!NXt;kbPGgyAP~TF&@1}{yik;gSp^NFbzxv zQ@}(Z+vhki7K{NoU?>;@1_RkjW!+|h0iZ9Ck?sz}4T(3IstnK*NGzrU;nDNWge>|@ z&>su}Qeif@0}KPh!3Z!Cj0U4XE|C3Q_Vh_WoRxGQ5MD4H%m%Z7Y%(*!98dt}fqeeS z(`3wJhxBd%xC<-<%fT{mFA%Stk&+RTQCSUS1RenQ1L^rHV1S3fgFpc(_Xv=3VzUm& zFgcFM~bcF|Yy1(8()JovNs(<;(GJBiIBsgZkic&;ZEsQKX;|*bDZ9eL$-C z40#gBs+XDh6s!fsK(@FOK>XgZIFHz!90H_en^j$H7NH3VjTYflq)`D!q|L z#Ube>o|iZ-`Hup-M$hU^Gr{}%o?(1CP*d*0fCYY;z!pMVcN z1=In*qhA7TNw)z%lGc#2Fa1DToQNPT`_ZqYWp8Q)?B_rkr61{Xjxs_6dM*z zzG)~9NCgt)BE`Grc3J{C8bpCe;4~&9A$h{z%t;-RCk{*6304_l$0;fQgX~QXDISTj z6P@ws;;C>lWa6Y0l97@{2;!s%| zX-p~;hh>?`GQPRYPlYMDb9!^BfxMV z4h;j@Kpb+0{m&Yd(UCcq_>cgXjaXuVX{fR7(tP%#RS`1v#=!LeeFo=ydEqqD~ z(z#iqLPQKw@j-eVA2a8Qn8}?U3g3-kMGCKLSkOFTVa_lL%V_bsSDhDwO(bh!u%-E3 zOZ!|3X{76BE^oV4T`$!hQmwptoc@hVP4Md7^~)~xvR4n%+Nh&meUyGtQz^lEP{^-z zNZQdifB*K#(YqpaEj}tHDuHVJ)#PBRsiu}8YBZ(#%Gkx-KYZ5ywd?2VTBzC?tcS

BS%qP`=mY3AiU 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 D7xrKI diff --git a/components/BackgroundWrapper.tsx b/components/BackgroundWrapper.tsx index c874760..e7139e0 100644 --- a/components/BackgroundWrapper.tsx +++ b/components/BackgroundWrapper.tsx @@ -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 (

{ >
{children}
diff --git a/components/DestructibleAlert.tsx b/components/DestructibleAlert.tsx index 36e6ad6..20eff0d 100644 --- a/components/DestructibleAlert.tsx +++ b/components/DestructibleAlert.tsx @@ -1,12 +1,14 @@ import React from "react"; +interface DestructibleAlertProps { + text: string; + extraStyles?: string; +} + const DestructibleAlert = ({ text, extraStyles = "", -}: { - text: string; - extraStyles?: string; -}) => { +}: DestructibleAlertProps) => { return (
{ + 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 (
-
+
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 && ( diff --git a/components/Header.tsx b/components/Header.tsx index a9c7f19..198ea1b 100644 --- a/components/Header.tsx +++ b/components/Header.tsx @@ -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(); useEffect(() => { diff --git a/components/QuestionItem.tsx b/components/QuestionItem.tsx index 8ba935e..64557a3 100644 --- a/components/QuestionItem.tsx +++ b/components/QuestionItem.tsx @@ -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 (

@@ -45,19 +57,14 @@ const QuestionItem = (props: QuestionItemProps) => { {isExam ? (
- {Object.entries(question.options).map(([key, value]) => { + {Object.entries(question.options ?? {}).map(([key, value]) => { const isSelected = selectedAnswer === key; return (
+
- {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) => { ); })}
+
+

Solution:

{question.solution}

diff --git a/components/SlidingGallery.tsx b/components/SlidingGallery.tsx index baa20a7..6abc755 100644 --- a/components/SlidingGallery.tsx +++ b/components/SlidingGallery.tsx @@ -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(0); const [dimensions, setDimensions] = useState({ width: 0, height: 0 }); - const scrollRef = useRef(null); - const galleryRef = useRef(null); - const autoScrollRef = useRef(null); + + const scrollRef = useRef(null); + const galleryRef = useRef(null); + const autoScrollRef = useRef(null); + + const handleScroll = (event: UIEvent) => { + 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 (
@@ -138,6 +141,8 @@ const SlidingGallery = ({ style={{ width: "100%", height: "100%", + overflowX: "scroll", + display: "flex", }} > {views.map((item) => ( @@ -154,6 +159,7 @@ const SlidingGallery = ({
))}
+ {showPagination && views.length > 1 && (
{views.map((_, index) => ( diff --git a/context/ExamContext.tsx b/context/ExamContext.tsx index f219fbf..b593920 100644 --- a/context/ExamContext.tsx +++ b/context/ExamContext.tsx @@ -86,7 +86,7 @@ export const ExamProvider: React.FC<{ children: ReactNode }> = ({ setCurrentAttemptState(null); }; - const startExam = (exam?: Exam) => { + const startExam = (exam?: Exam): void => { const examToUse = exam || currentExam; if (!examToUse) { @@ -101,6 +101,7 @@ export const ExamProvider: React.FC<{ children: ReactNode }> = ({ answers: [], startTime: new Date(), totalQuestions: 0, + score: 0, }; setCurrentAttemptState(attempt); diff --git a/context/ModalContext.tsx b/context/ModalContext.tsx index 6c9e49f..b08d85e 100644 --- a/context/ModalContext.tsx +++ b/context/ModalContext.tsx @@ -1,24 +1,41 @@ "use client"; import { createContext, useContext, useState } from "react"; -const ModalContext = createContext(null); +// Define the context type +interface ModalContextType { + isOpen: boolean; + open: () => void; + close: () => void; + toggle: () => void; +} -export function ModalProvider({ children }) { +// Create context with default values (no null) +const ModalContext = createContext({ + isOpen: false, + open: () => {}, + close: () => {}, + toggle: () => {}, +}); + +export function ModalProvider({ children }: { children: React.ReactNode }) { const [isOpen, setIsOpen] = useState(false); const open = () => setIsOpen(true); const close = () => setIsOpen(false); const toggle = () => setIsOpen((prev) => !prev); + const value: ModalContextType = { + isOpen, + open, + close, + toggle, + }; + return ( - - {children} - + {children} ); } -export function useModal() { - const ctx = useContext(ModalContext); - if (!ctx) throw new Error("useModal must be inside "); - return ctx; +export function useModal(): ModalContextType { + return useContext(ModalContext); } diff --git a/eslint.config.mjs b/eslint.config.mjs index c85fb67..c2d45e9 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -11,6 +11,15 @@ const compat = new FlatCompat({ const eslintConfig = [ ...compat.extends("next/core-web-vitals", "next/typescript"), + { + rules: { + // Disable the no-explicit-any rule + "@typescript-eslint/no-explicit-any": "off", + + // Alternative: Make it a warning instead of error + // "@typescript-eslint/no-explicit-any": "warn", + }, + }, ]; export default eslintConfig; diff --git a/lib/auth.ts b/lib/auth.ts index f9d4378..72e2347 100644 --- a/lib/auth.ts +++ b/lib/auth.ts @@ -1,20 +1,37 @@ export const API_URL = "https://examjam-api.pptx704.com"; -// Cookie utility functions -const setCookie = (name, value, days = 7) => { +// Cookie utility function +const setCookie = (name: string, value: string | null, days: number = 7) => { if (typeof document === "undefined") return; if (value === null) { - // Delete cookie by setting expiration to past date document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/; SameSite=Strict; Secure`; } else { const expires = new Date(); expires.setTime(expires.getTime() + days * 24 * 60 * 60 * 1000); - document.cookie = `${name}=${value}; expires=${expires.toUTCString()}; path=/; SameSite=Strict; Secure`; + document.cookie = `${name}=${encodeURIComponent( + value + )}; expires=${expires.toUTCString()}; path=/; SameSite=Strict; Secure`; } }; -export const login = async (form, setToken) => { +interface AuthForm { + email: string; + password: string; + [key: string]: any; // for flexibility +} + +type SetTokenFn = (token: string) => void; + +// Optional: Create a custom error type to carry extra data +interface APIError extends Error { + response?: any; +} + +export const login = async ( + form: AuthForm, + setToken: SetTokenFn +): Promise => { const response = await fetch(`${API_URL}/auth/login`, { method: "POST", headers: { @@ -29,27 +46,14 @@ export const login = async (form, setToken) => { throw new Error(data.message || "Login failed"); } - // Save the token to cookies instead of secure storage setCookie("authToken", data.token); - setToken(data.token); // Update the token in context + setToken(data.token); }; -const handleError = (error) => { - // Check if error has a "detail" property - if (error?.detail) { - // Match the field causing the issue - const match = error.detail.match(/Key \((.*?)\)=\((.*?)\)/); - - if (match) { - const field = match[1]; // The field name, e.g., "phone" - const value = match[2]; // The duplicate value, e.g., "0987654321" - return `The ${field} already exists. Please use a different value.`; - } - } - return "An unexpected error occurred. Please try again."; -}; - -export const register = async (form, setToken) => { +export const register = async ( + form: AuthForm, + setToken: SetTokenFn +): Promise => { const response = await fetch(`${API_URL}/auth/register`, { method: "POST", headers: { @@ -58,22 +62,19 @@ export const register = async (form, setToken) => { body: JSON.stringify(form), }); - const data = await response.json(); // Parse the response JSON + const data = await response.json(); if (!response.ok) { - // Instead of throwing a string, include full error data for debugging - const error = new Error(data?.detail || "Registration failed"); - error.response = data; // Attach the full response for later use + const error: APIError = new Error(data?.detail || "Registration failed"); + error.response = data; throw error; } - // Save the token to cookies instead of secure storage setCookie("authToken", data.token); - setToken(data.token); // Update the token in context + setToken(data.token); }; -// Additional utility function to get token from cookies (if needed elsewhere) -export const getTokenFromCookie = () => { +export const getTokenFromCookie = (): string | null => { if (typeof document === "undefined") return null; const value = `; ${document.cookie}`; @@ -84,17 +85,15 @@ export const getTokenFromCookie = () => { return null; }; -// Utility function to clear auth token (for logout) -export const clearAuthToken = () => { +export const clearAuthToken = (): void => { setCookie("authToken", null); }; -export const getToken = async () => { +export const getToken = async (): Promise => { if (typeof window === "undefined") { return null; } - // Extract authToken from cookies const match = document.cookie.match(/(?:^|;\s*)authToken=([^;]*)/); return match ? decodeURIComponent(match[1]) : null; }; diff --git a/lib/gallery-views.tsx b/lib/gallery-views.tsx index e5f0195..dc69bae 100644 --- a/lib/gallery-views.tsx +++ b/lib/gallery-views.tsx @@ -1,16 +1,14 @@ // lib/gallery-views.tsx import Link from "next/link"; import Image from "next/image"; +import { ExamAnswer } from "@/types/exam"; +import { GalleryViews } from "@/types/gallery"; +// Define the ExamResults type if not already defined interface ExamResults { score: number; totalQuestions: number; - answers: string[]; -} - -interface LinkedViews { - id: string; - content: React.ReactNode; + answers: ExamAnswer[]; // or more specific type based on your answer structure } export const getResultViews = (examResults: ExamResults | null) => [ @@ -104,9 +102,9 @@ export const getResultViews = (examResults: ExamResults | null) => [ }, ]; -export const getLinkedViews = (): LinkedViews[] => [ +export const getLinkedViews = (): GalleryViews[] => [ { - id: "1", + id: 1, content: ( ; type: "multiple-choice" | "text" | "boolean" | undefined; correctAnswer: string | undefined; @@ -18,7 +18,7 @@ export interface Exam { export interface ExamAnswer { questionId: string; - answer: any; + answer: string; timestamp: Date; } @@ -28,7 +28,7 @@ export interface ExamAttempt { answers: ExamAnswer[]; startTime: Date; endTime?: Date; - score?: number; + score: number; passed?: boolean; apiResponse?: any; totalQuestions: number; @@ -42,9 +42,9 @@ export interface ExamContextType { // Actions setCurrentExam: (exam: Exam) => void; - startExam: () => void; + startExam: (exam?: Exam) => void; setAnswer: (questionId: string, answer: any) => void; - submitExam: () => ExamAttempt; + submitExam: () => ExamAttempt | null; clearExam: () => void; setApiResponse: (response: any) => void; diff --git a/types/gallery.d.ts b/types/gallery.d.ts new file mode 100644 index 0000000..581d375 --- /dev/null +++ b/types/gallery.d.ts @@ -0,0 +1,4 @@ +export interface GalleryViews { + id: number; + content: React.JSX.Element; +}