Skip to content

Commit f142444

Browse files
chore: update dependencies and configurations; enhance Next.js setup with turbopack and improved image handling; adjust API version for Stripe integration; refine TypeScript settings and component imports for better compatibility
1 parent 6c11229 commit f142444

File tree

19 files changed

+8360
-5738
lines changed

19 files changed

+8360
-5738
lines changed

.github/workflows/vercel-deploy.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ jobs:
3434
- name: Setup Node.js
3535
uses: actions/setup-node@v2
3636
with:
37-
node-version: "20.x"
37+
node-version: "24.x"
3838

3939
- name: Install Vercel CLI
4040
run: npm install --global vercel@latest

app/api/get-ip/route.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ export async function GET(request: NextRequest) {
1212

1313
let ip = forwarded?.split(",")[0] || realIp || cfConnectingIp;
1414

15-
// If no IP found in headers, try to get from connection
15+
// If no IP found in headers, use fallback (request.ip removed in Next 15+)
1616
if (!ip) {
17-
ip = request.ip || "127.0.0.1";
17+
ip = "127.0.0.1";
1818
}
1919

2020
// Clean up the IP (remove port if present)

app/api/profile/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ export async function GET(request: NextRequest) {
170170
const stripeSecretKey = process.env.NEXT_PUBLIC_STRIPE_SECRET_KEY;
171171
if (stripeSecretKey) {
172172
const stripe = new Stripe(stripeSecretKey, {
173-
apiVersion: "2025-11-17.clover",
173+
apiVersion: "2025-12-15.clover" as Stripe.LatestApiVersion,
174174
});
175175
const stripeSubscription = (await stripe.subscriptions.retrieve(
176176
latestSubscription.stripe_subscription_id,

app/api/stripe/create-checkout-session/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ export async function POST(request: NextRequest) {
4141

4242
// Initialize Stripe
4343
const stripe = new Stripe(stripeSecretKey, {
44-
apiVersion: "2025-11-17.clover",
44+
apiVersion: "2025-12-15.clover" as Stripe.LatestApiVersion,
4545
});
4646

4747
// Get the base URL for redirects

app/api/stripe/webhook/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ export async function POST(request: NextRequest) {
138138
}
139139

140140
const stripe = new Stripe(stripeSecretKey, {
141-
apiVersion: "2025-11-17.clover",
141+
apiVersion: "2025-12-15.clover" as Stripe.LatestApiVersion,
142142
});
143143

144144
let event: Stripe.Event;

app/exam/page.tsx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@
22

33
import { useEffect, useState } from "react";
44
import type { NextPage } from "next";
5-
import { gql, useQuery } from "@apollo/client";
5+
import { gql } from "@apollo/client";
6+
import { useQuery } from "@apollo/client/react";
67
import useTimer from "@azure-fundamentals/hooks/useTimer";
78
import { Button } from "@azure-fundamentals/components/Button";
89
import QuizExamForm from "@azure-fundamentals/components/QuizExamFormUF";
@@ -27,16 +28,23 @@ const questionsQuery = gql`
2728
}
2829
`;
2930

31+
type RandomQuestionsData = {
32+
randomQuestions: Question[];
33+
};
34+
3035
const Exam: NextPage = () => {
3136
const { isAccessBlocked, isInTrial } = useTrialAccess();
3237
const [searchParams, setSearchParams] = useState<URLSearchParams | null>(
3338
null,
3439
);
3540
const url = searchParams?.get("url") || "";
36-
const { data, loading, error } = useQuery(questionsQuery, {
37-
variables: { range: 30, link: url },
38-
skip: !url, // Skip query if URL is not available
39-
});
41+
const { data, loading, error } = useQuery<RandomQuestionsData>(
42+
questionsQuery,
43+
{
44+
variables: { range: 30, link: url },
45+
skip: !url, // Skip query if URL is not available
46+
},
47+
);
4048
// Calculate timer: 2 minutes per question (30 questions = 60 minutes)
4149
const examQuestionCount = 30; // Fixed number of questions in exam mode
4250
const minutesPerQuestion = 2;
@@ -81,7 +89,7 @@ const Exam: NextPage = () => {
8189
};
8290

8391
const getResultPoints = (points: number) => {
84-
const maxPoints = data?.randomQuestions?.length;
92+
const maxPoints = data?.randomQuestions?.length ?? 1;
8593
const percentage = Math.round((points / maxPoints) * 10000) / 100;
8694
if (percentage >= 75) {
8795
setPassed(true);

app/practice/page.tsx

Lines changed: 18 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
"use client";
22

33
import { useState, useEffect, useCallback } from "react";
4-
import { gql, useQuery } from "@apollo/client";
4+
import { gql } from "@apollo/client";
5+
import { useQuery } from "@apollo/client/react";
56
import type { NextPage } from "next";
67
import QuizForm from "@azure-fundamentals/components/QuizForm";
78
import { useTrialAccess } from "@azure-fundamentals/hooks/useTrialAccess";
@@ -31,6 +32,13 @@ const questionsQuery = gql`
3132
}
3233
`;
3334

35+
type QuestionByIdData = {
36+
questionById: import("@azure-fundamentals/components/types").Question | null;
37+
};
38+
type QuestionsCountData = {
39+
questions: { count: number };
40+
};
41+
3442
const Practice: NextPage = () => {
3543
const { isAccessBlocked, isInTrial } = useTrialAccess();
3644
const [searchParams, setSearchParams] = useState<URLSearchParams | null>(
@@ -44,7 +52,7 @@ const Practice: NextPage = () => {
4452
const editedUrl =
4553
url && url.includes("/") ? url.substring(0, url.lastIndexOf("/") + 1) : "";
4654

47-
const { loading, error, data } = useQuery(questionQuery, {
55+
const { loading, error, data } = useQuery<QuestionByIdData>(questionQuery, {
4856
variables: { id: currentQuestionIndex - 1, link: url },
4957
skip: !url, // Skip query if URL is not available
5058
});
@@ -58,7 +66,7 @@ const Practice: NextPage = () => {
5866
data: questionsData,
5967
loading: questionsLoading,
6068
error: questionsError,
61-
} = useQuery(questionsQuery, {
69+
} = useQuery<QuestionsCountData>(questionsQuery, {
6270
variables: { link: url },
6371
skip: !url, // Skip query if URL is not available
6472
});
@@ -199,7 +207,13 @@ const Practice: NextPage = () => {
199207
)}
200208
<QuizForm
201209
isLoading={loading || questionsLoading}
202-
questionSet={data?.questionById}
210+
questionSet={
211+
data?.questionById ?? {
212+
question: "",
213+
options: [],
214+
images: [],
215+
}
216+
}
203217
handleNextQuestion={handleNextQuestion}
204218
totalQuestions={Math.max(0, (questionsData?.questions?.count || 0) - 1)}
205219
currentQuestionIndex={currentQuestionIndex}

components/ApolloProvider.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
import client from "@azure-fundamentals/lib/graphql/apollo-client";
44
import { ReactNode } from "react";
5-
import { ApolloProvider as NextApolloProvider } from "@apollo/client";
5+
import { ApolloProvider as NextApolloProvider } from "@apollo/client/react";
66

77
type RootLayoutProps = {
88
children: ReactNode;

components/Footer.tsx

Lines changed: 62 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"use client";
22

3+
import React from "react";
34
import {
45
SiDiscord,
56
SiGithub,
@@ -21,44 +22,50 @@ const Footer = () => {
2122
const iconSize = 28;
2223
const { theme } = useTheme();
2324

24-
const socialMediaLinks = [
25-
{
26-
url: "https://discord.gg/RFjtXKfJy3",
27-
icon: <SiDiscord className="discord" size={iconSize} />,
28-
},
29-
{
30-
url: "https://github.com/ditectrev",
31-
icon: <SiGithub className="github" size={iconSize} />,
32-
},
33-
{
34-
url: "https://instagram.com/ditectrev",
35-
icon: <SiInstagram className="instagram" size={iconSize} />,
36-
},
37-
{
38-
url: "https://linkedin.com/company/ditectrev",
39-
icon: <SiLinkedin className="linkedin" size={iconSize} />,
40-
},
41-
{
42-
url: "https://medium.com/@ditectrev",
43-
icon: <SiMedium className="medium" size={iconSize} />,
44-
},
45-
{
46-
url: "https://patreon.com/Ditectrev",
47-
icon: <SiPatreon className="patreon" size={iconSize} />,
48-
},
49-
{
50-
url: "https://udemy.com/user/social-ditectrev",
51-
icon: <SiUdemy className="udemy" size={iconSize} />,
52-
},
53-
{
54-
url: "https://x.com/ditectrev",
55-
icon: <SiX className="x" size={iconSize} />,
56-
},
57-
{
58-
url: "https://youtube.com/@Ditectrev",
59-
icon: <SiYoutube className="youtube" size={iconSize} />,
60-
},
61-
];
25+
const socialMediaLinks: Array<{ url: string; Icon: any; className: string }> =
26+
[
27+
{
28+
url: "https://discord.gg/RFjtXKfJy3",
29+
Icon: SiDiscord,
30+
className: "discord",
31+
},
32+
{
33+
url: "https://github.com/ditectrev",
34+
Icon: SiGithub,
35+
className: "github",
36+
},
37+
{
38+
url: "https://instagram.com/ditectrev",
39+
Icon: SiInstagram,
40+
className: "instagram",
41+
},
42+
{
43+
url: "https://linkedin.com/company/ditectrev",
44+
Icon: SiLinkedin,
45+
className: "linkedin",
46+
},
47+
{
48+
url: "https://medium.com/@ditectrev",
49+
Icon: SiMedium,
50+
className: "medium",
51+
},
52+
{
53+
url: "https://patreon.com/Ditectrev",
54+
Icon: SiPatreon,
55+
className: "patreon",
56+
},
57+
{
58+
url: "https://udemy.com/user/social-ditectrev",
59+
Icon: SiUdemy,
60+
className: "udemy",
61+
},
62+
{ url: "https://x.com/ditectrev", Icon: SiX, className: "x" },
63+
{
64+
url: "https://youtube.com/@Ditectrev",
65+
Icon: SiYoutube,
66+
className: "youtube",
67+
},
68+
];
6269

6370
const gradientClass =
6471
theme === "dark"
@@ -69,18 +76,22 @@ const Footer = () => {
6976
<footer className={`relative ${gradientClass} overflow-hidden`}>
7077
<ParticlesFooter />
7178
<div className="relative z-10 mx-3 my-3 social-icons-container text-white">
72-
{socialMediaLinks.map((link, index) => (
73-
<a
74-
key={index}
75-
className="px-2"
76-
href={link.url}
77-
target="_blank"
78-
rel="noopener noreferrer"
79-
aria-label={`Visit ${link.url}`}
80-
>
81-
{link.icon}
82-
</a>
83-
))}
79+
{socialMediaLinks.map((link, index) => {
80+
const IconComponent = link.Icon;
81+
return (
82+
<a
83+
key={index}
84+
className="px-2"
85+
href={link.url}
86+
target="_blank"
87+
rel="noopener noreferrer"
88+
aria-label={`Visit ${link.url}`}
89+
>
90+
{/* @ts-ignore - react-icons types incompatible with React 18.3 strict types */}
91+
<IconComponent className={link.className} size={iconSize} />
92+
</a>
93+
);
94+
})}
8495
</div>
8596

8697
{/* GitHub Star */}
@@ -104,7 +115,7 @@ const Footer = () => {
104115

105116
{/* Copyright */}
106117
<p className="relative z-10 text-white text-sm flex justify-center">
107-
&copy; 2026 Ditectrev and its contributors
118+
&copy; {currentYear} Ditectrev and its contributors
108119
</p>
109120
</footer>
110121
);

components/Header.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ const Header = () => {
6262
];
6363

6464
const ExternalLinkIcon = () => (
65+
// @ts-ignore - react-icons types incompatible with React 18.3 strict types
6566
<FiExternalLink className="inline-block ml-1 w-3 h-3 external-link-icon" />
6667
);
6768

0 commit comments

Comments
 (0)