Skip to content

Commit bd4f545

Browse files
Merge pull request #80 from Ditectrev/feature/theme-toggle
Feature/theme toggle
2 parents f85a0e0 + 3256e7c commit bd4f545

31 files changed

+1616
-216
lines changed

app/error.tsx

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"use client";
2+
3+
import { useEffect } from "react";
4+
import Link from "next/link";
5+
6+
export default function Error({
7+
error,
8+
reset,
9+
}: {
10+
error: Error & { digest?: string };
11+
reset: () => void;
12+
}) {
13+
useEffect(() => {
14+
// Log the error to an error reporting service
15+
console.error("Application error:", error);
16+
}, [error]);
17+
18+
return (
19+
<div className="min-h-screen bg-[var(--color-background)] flex flex-col items-center justify-center px-4">
20+
{/* Header */}
21+
<h1 className="text-2xl font-semibold text-[var(--color-text-primary)] mb-4">
22+
Something went wrong!
23+
</h1>
24+
25+
{/* Description */}
26+
<p className="zoom-area text-[var(--color-text-secondary)] text-lg mb-8 max-w-md text-center">
27+
We&apos;re sorry, but something unexpected happened. Don&apos;t worry,
28+
our <b className="text-[var(--color-primary)]">practice exams</b> are
29+
still here waiting for you!
30+
</p>
31+
32+
{/* Error Details (only in development) */}
33+
{process.env.NODE_ENV === "development" && error.message && (
34+
<div className="mb-6 p-4 bg-red-50 dark:bg-red-900/20 border border-red-200 dark:border-red-800 rounded-lg max-w-md w-full">
35+
<p className="text-sm text-red-800 dark:text-red-200 font-mono break-all">
36+
{error.message}
37+
</p>
38+
</div>
39+
)}
40+
41+
{/* Action buttons */}
42+
<div className="flex flex-col sm:flex-row gap-4 items-center">
43+
<button
44+
onClick={reset}
45+
className="px-6 py-3 bg-[var(--color-primary)] text-white rounded-lg hover:opacity-90 transition-opacity font-medium"
46+
>
47+
Try again
48+
</button>
49+
<Link
50+
href="/"
51+
className="px-6 py-3 bg-gray-200 dark:bg-gray-700 text-gray-900 dark:text-gray-100 rounded-lg hover:opacity-90 transition-opacity font-medium"
52+
>
53+
📚 Go to Home
54+
</Link>
55+
</div>
56+
57+
{/* Help text */}
58+
<p className="mt-8 text-sm text-[var(--color-text-secondary)] text-center max-w-md">
59+
If this problem persists, please{" "}
60+
<a
61+
href="https://github.com/Ditectrev/Practice-Exams-Platform/issues"
62+
target="_blank"
63+
rel="noopener noreferrer"
64+
className="text-[var(--color-primary)] hover:underline"
65+
>
66+
report it on GitHub
67+
</a>
68+
.
69+
</p>
70+
</div>
71+
);
72+
}

app/exam/page.tsx

Lines changed: 94 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,12 @@ const questionsQuery = gql`
2727
}
2828
`;
2929

30-
const Exam: NextPage<{ searchParams: { url: string; name: string } }> = ({
31-
searchParams,
32-
}) => {
30+
const Exam: NextPage = () => {
3331
const { isAccessBlocked, isInTrial } = useTrialAccess();
34-
const { url } = searchParams;
32+
const [searchParams, setSearchParams] = useState<URLSearchParams | null>(
33+
null,
34+
);
35+
const url = searchParams?.get("url") || "";
3536
const { minutes, seconds } = {
3637
minutes: 15,
3738
seconds: 0,
@@ -45,16 +46,23 @@ const Exam: NextPage<{ searchParams: { url: string; name: string } }> = ({
4546
const [countAnswered, setCountAnswered] = useState<number>(0);
4647
const { data, loading, error } = useQuery(questionsQuery, {
4748
variables: { range: 30, link: url },
49+
skip: !url, // Skip query if URL is not available
4850
});
4951
const [resultPoints, setResultPoints] = useState<number>(0);
5052
const [passed, setPassed] = useState<boolean>(false);
5153
const [windowWidth, setWindowWidth] = useState<number>(0);
52-
const editedUrl = url.substring(0, url.lastIndexOf("/") + 1);
54+
const editedUrl =
55+
url && url.includes("/") ? url.substring(0, url.lastIndexOf("/") + 1) : "";
5356
const elapsedSeconds =
5457
totalTimeInSeconds -
5558
(parseInt(remainingTime.split(":")[0]) * 60 +
5659
parseInt(remainingTime.split(":")[1]));
5760

61+
useEffect(() => {
62+
const param = new URLSearchParams(window.location.search);
63+
setSearchParams(param);
64+
}, []);
65+
5866
const handleCountAnswered = () => {
5967
setCountAnswered(countAnswered + 1);
6068
};
@@ -89,35 +97,97 @@ const Exam: NextPage<{ searchParams: { url: string; name: string } }> = ({
8997
setCurrentQuestion(data?.randomQuestions[0]);
9098
}, [data]);
9199

92-
// Show loading while checking trial access
93-
if (isAccessBlocked === undefined) {
100+
// Show loading while checking trial access or waiting for URL
101+
if (isAccessBlocked === undefined || !searchParams) {
94102
return <LoadingIndicator />;
95103
}
96104

105+
// Check if URL is missing
106+
if (!url) {
107+
return (
108+
<div className="min-h-[calc(100vh-8rem)] flex items-center justify-center px-4">
109+
<div className="py-10 px-5 mb-6 mx-auto w-[90vw] lg:w-[60vw] 2xl:w-[45%] bg-white dark:bg-gray-800 border-2 border-gray-200 dark:border-gray-700 rounded-lg text-center">
110+
<div className="text-red-500 dark:text-red-400 text-lg mb-4">
111+
⚠️ Exam URL is missing. Please select an exam from the home page.
112+
</div>
113+
<button
114+
onClick={() => (window.location.href = "/")}
115+
className="btn-primary text-white px-6 py-2 rounded-lg"
116+
>
117+
Go to Home
118+
</button>
119+
</div>
120+
</div>
121+
);
122+
}
123+
97124
// Block access if trial expired
98125
if (isAccessBlocked) {
99126
return (
100-
<div className="py-10 px-5 mb-6 mx-auto w-[90vw] lg:w-[60vw] 2xl:w-[45%] bg-slate-800 border-2 border-slate-700 rounded-lg text-center">
101-
<div className="text-red-400 text-lg mb-4">
102-
⏰ Trial expired. Please sign in to continue taking exams.
127+
<div className="min-h-[calc(100vh-8rem)] flex items-center justify-center px-4">
128+
<div className="py-10 px-5 mb-6 mx-auto w-[90vw] lg:w-[60vw] 2xl:w-[45%] bg-white dark:bg-gray-800 border-2 border-gray-200 dark:border-gray-700 rounded-lg text-center">
129+
<div className="text-red-500 dark:text-red-400 text-lg mb-4">
130+
⏰ Trial expired. Please sign in to continue taking exams.
131+
</div>
132+
<button
133+
onClick={() => (window.location.href = "/")}
134+
className="btn-primary text-white px-6 py-2 rounded-lg"
135+
>
136+
Go to Home
137+
</button>
103138
</div>
104-
<button
105-
onClick={() => (window.location.href = "/")}
106-
className="btn-primary text-white px-6 py-2 rounded-lg"
107-
>
108-
Go to Home
109-
</button>
110139
</div>
111140
);
112141
}
113142

114143
if (loading) return <LoadingIndicator />;
115-
if (error) return <p>Oh no... {error.message}</p>;
144+
if (error) {
145+
return (
146+
<div className="min-h-[calc(100vh-8rem)] flex items-center justify-center px-4">
147+
<div className="py-10 px-5 mb-6 mx-auto w-[90vw] lg:w-[60vw] 2xl:w-[45%] bg-white dark:bg-gray-800 border-2 border-red-200 dark:border-red-700 rounded-lg text-center">
148+
<div className="text-red-500 dark:text-red-400 text-lg mb-4">
149+
⚠️ Error loading exam questions
150+
</div>
151+
<p className="text-gray-700 dark:text-gray-300 mb-4">
152+
{error.message}
153+
</p>
154+
<button
155+
onClick={() => (window.location.href = "/")}
156+
className="btn-primary text-white px-6 py-2 rounded-lg"
157+
>
158+
Go to Home
159+
</button>
160+
</div>
161+
</div>
162+
);
163+
}
164+
165+
if (!data?.randomQuestions || data.randomQuestions.length === 0) {
166+
return (
167+
<div className="min-h-[calc(100vh-8rem)] flex items-center justify-center px-4">
168+
<div className="py-10 px-5 mb-6 mx-auto w-[90vw] lg:w-[60vw] 2xl:w-[45%] bg-white dark:bg-gray-800 border-2 border-yellow-200 dark:border-yellow-700 rounded-lg text-center">
169+
<div className="text-yellow-600 dark:text-yellow-400 text-lg mb-4">
170+
⚠️ No questions found for this exam
171+
</div>
172+
<p className="text-gray-700 dark:text-gray-300 mb-4">
173+
The exam questions could not be loaded. Please try again later or
174+
select a different exam.
175+
</p>
176+
<button
177+
onClick={() => (window.location.href = "/")}
178+
className="btn-primary text-white px-6 py-2 rounded-lg"
179+
>
180+
Go to Home
181+
</button>
182+
</div>
183+
</div>
184+
);
185+
}
116186

117187
const numberOfQuestions = data.randomQuestions.length || 0;
118188

119189
return (
120-
<div className="py-10 px-5 mb-6 mx-auto w-[90vw] lg:w-[60vw] 2xl:w-[45%] bg-slate-800 border-2 border-slate-700 rounded-lg">
190+
<div className="py-10 px-5 mb-6 mx-auto w-[90vw] lg:w-[60vw] 2xl:w-[45%] bg-white dark:bg-gray-800 border-2 border-gray-200 dark:border-gray-700 rounded-lg mt-8">
121191
{isInTrial && (
122192
<div className="mb-6 p-4 bg-amber-600/20 border border-amber-600/40 rounded-lg">
123193
<div className="flex items-center gap-2 text-amber-300">
@@ -142,13 +212,13 @@ const Exam: NextPage<{ searchParams: { url: string; name: string } }> = ({
142212
)}
143213
<div>
144214
<div className="px-2 sm:px-10 w-full flex flex-row justify-between items-center">
145-
<p className="text-white font-bold text-sm sm:text-2xl">
215+
<p className="text-gray-900 dark:text-white font-bold text-sm sm:text-2xl">
146216
{countAnswered}/{numberOfQuestions}
147217
</p>
148-
<h1 className="text-white font-bold text-lg sm:text-3xl">
218+
<h1 className="text-gray-900 dark:text-white font-bold text-lg sm:text-3xl">
149219
PRACTICE EXAM
150220
</h1>
151-
<p className="text-white font-bold text-sm sm:text-2xl">
221+
<p className="text-gray-900 dark:text-white font-bold text-sm sm:text-2xl">
152222
{remainingTime}
153223
</p>
154224
</div>
@@ -202,7 +272,7 @@ const Exam: NextPage<{ searchParams: { url: string; name: string } }> = ({
202272
c0.53,0,0.97,0.43,0.97,0.97V29.35z"
203273
/>
204274
</svg>
205-
<p className="text-white text-center pt-6 px-6">
275+
<p className="text-gray-900 dark:text-white text-center pt-6 px-6">
206276
Practice Exam help you practice skills, assess your knowledge,
207277
and identify the areas where you need additional preparation
208278
to accelerate your chances of succeeding on certification
@@ -211,7 +281,7 @@ const Exam: NextPage<{ searchParams: { url: string; name: string } }> = ({
211281
are likely to experience on Azure Fundamentals real exam.
212282
</p>
213283
</div>
214-
<p className="text-white font-bold text-xl text-center pt-20 px-6 mb-40">
284+
<p className="text-gray-900 dark:text-white font-bold text-xl text-center pt-20 px-6 mb-40">
215285
This Practice Exam contains {numberOfQuestions} random questions
216286
(seen in upper left corner) and has a completion time limit of{" "}
217287
{remainingTime.split(":")[0]} minutes (seen in upper right
@@ -221,7 +291,7 @@ const Exam: NextPage<{ searchParams: { url: string; name: string } }> = ({
221291
<div className="flex flex-col sm:flex-row justify-center">
222292
<Button
223293
type="button"
224-
intent="secondary"
294+
intent="primary"
225295
size="medium"
226296
onClick={() => startTimer()}
227297
>

app/layout.tsx

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import Footer from "@azure-fundamentals/components/Footer";
55
import ApolloProvider from "@azure-fundamentals/components/ApolloProvider";
66
import Cookie from "@azure-fundamentals/components/Cookie";
77
import { AuthProvider } from "@azure-fundamentals/contexts/AuthContext";
8+
import { ThemeProvider } from "@azure-fundamentals/contexts/ThemeContext";
89
import { TrialWarning } from "@azure-fundamentals/components/TrialWarning";
910
import "styles/globals.css";
1011

@@ -105,19 +106,21 @@ type RootLayoutProps = {
105106

106107
export default function RootLayout({ children }: RootLayoutProps) {
107108
return (
108-
<html lang="en">
109-
<body className="bg-slate-900">
110-
<ApolloProvider>
111-
<AuthProvider>
112-
<Header />
113-
<main className="flex flex-col justify-between min-h-[calc(100vh-4rem)]">
114-
{children}
115-
<Footer />
116-
<Cookie />
117-
<TrialWarning />
118-
</main>
119-
</AuthProvider>
120-
</ApolloProvider>
109+
<html lang="en" className="dark">
110+
<body className="bg-gray-50 dark:bg-gray-950 text-gray-900 dark:text-gray-100 transition-colors duration-200">
111+
<ThemeProvider>
112+
<ApolloProvider>
113+
<AuthProvider>
114+
<Header />
115+
<main className="flex flex-col justify-between min-h-[calc(100vh-4rem)]">
116+
{children}
117+
<Footer />
118+
<Cookie />
119+
<TrialWarning />
120+
</main>
121+
</AuthProvider>
122+
</ApolloProvider>
123+
</ThemeProvider>
121124
</body>
122125
</html>
123126
);

0 commit comments

Comments
 (0)