Skip to content

Commit 63a4519

Browse files
committed
fix: coderabbit reviews
1 parent 635f378 commit 63a4519

30 files changed

Lines changed: 164 additions & 45 deletions

apps/api/src/services/user.service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ export const userService = {
5555
prisma: ExtendedPrismaClient | PrismaClient,
5656
userId: string
5757
) {
58-
const user = await (prisma as any).user.findUnique({
58+
const user = await prisma.user.findUnique({
5959
where: { id: userId },
6060
select: { completedSteps: true },
6161
});
@@ -76,7 +76,7 @@ export const userService = {
7676
userId: string,
7777
completedSteps: string[]
7878
) {
79-
const user = await (prisma as any).user.update({
79+
const user = await prisma.user.update({
8080
where: { id: userId },
8181
data: {
8282
completedSteps: completedSteps,

apps/web/package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
"@vercel/speed-insights": "^1.1.0",
2525
"class-variance-authority": "^0.7.0",
2626
"clsx": "^2.1.1",
27+
"dompurify": "^3.3.0",
2728
"framer-motion": "^11.15.0",
2829
"geist": "^1.5.1",
2930
"lucide-react": "^0.456.0",
@@ -41,6 +42,7 @@
4142
"zustand": "^5.0.1"
4243
},
4344
"devDependencies": {
45+
"@types/dompurify": "^3.2.0",
4446
"@types/node": "^20",
4547
"@types/react": "^18",
4648
"@types/react-dom": "^18",

apps/web/src/app/(main)/dashboard/sheet/page.tsx

Lines changed: 51 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -33,20 +33,62 @@ export default function SheetPage() {
3333
const { data: session, status } = useSession();
3434
const [completedSteps, setCompletedSteps] = useState<string[]>([]);
3535
const [copied, setCopied] = useState(false);
36+
const utils = trpc.useUtils();
3637

37-
const { data: fetchedSteps, isLoading: isLoadingSteps } = (
38-
trpc.user as any
39-
).getCompletedSteps.useQuery(undefined, {
40-
enabled: !!session?.user && status === "authenticated",
41-
refetchOnWindowFocus: false,
42-
});
38+
// TypeScript has difficulty narrowing TRPC procedure union types.
39+
// These procedures are correctly typed at runtime (query vs mutation).
40+
const getCompletedStepsProcedure = trpc.user
41+
.getCompletedSteps as typeof trpc.user.getCompletedSteps & {
42+
useQuery: (input: undefined, opts?: any) => any;
43+
};
44+
const updateCompletedStepsProcedure = trpc.user
45+
.updateCompletedSteps as typeof trpc.user.updateCompletedSteps & {
46+
useMutation: (opts?: any) => any;
47+
};
48+
const getCompletedStepsUtilsProcedure = utils.user
49+
.getCompletedSteps as typeof utils.user.getCompletedSteps & {
50+
cancel: () => Promise<void>;
51+
invalidate: () => Promise<void>;
52+
};
53+
54+
const { data: fetchedSteps, isLoading: isLoadingSteps } =
55+
getCompletedStepsProcedure.useQuery(undefined, {
56+
enabled: !!session?.user && status === "authenticated",
57+
refetchOnWindowFocus: false,
58+
});
59+
60+
const updateStepsMutation = updateCompletedStepsProcedure.useMutation({
61+
onMutate: async (newData: { completedSteps: string[] }) => {
62+
// Cancel any outgoing refetches to avoid overwriting optimistic update
63+
await getCompletedStepsUtilsProcedure.cancel();
4364

44-
const updateStepsMutation = (
45-
trpc.user as any
46-
).updateCompletedSteps.useMutation({
65+
// Snapshot the previous value
66+
const previousSteps = completedSteps;
67+
68+
// Optimistically update to the new value
69+
setCompletedSteps(newData.completedSteps);
70+
71+
// Return context with the previous value
72+
return { previousSteps };
73+
},
4774
onSuccess: (data: string[]) => {
4875
setCompletedSteps(data);
4976
},
77+
onError: (
78+
error: unknown,
79+
_newData: { completedSteps: string[] },
80+
context: { previousSteps: string[] } | undefined
81+
) => {
82+
console.error("Failed to update completed steps:", error);
83+
if (context?.previousSteps) {
84+
setCompletedSteps(context.previousSteps);
85+
} else if (fetchedSteps) {
86+
setCompletedSteps(fetchedSteps);
87+
}
88+
},
89+
onSettled: () => {
90+
getCompletedStepsUtilsProcedure.invalidate();
91+
},
5092
});
5193

5294
useEffect(() => {

apps/web/src/app/(main)/sheet/[moduleId]/page.tsx

Lines changed: 52 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,64 @@ import { sheetModules } from "@/data/sheet";
55
import Link from "next/link";
66
import { ArrowLeft, Download, Share2, Check } from "lucide-react";
77
import { Badge } from "@/components/ui/badge";
8-
import { useState } from "react";
8+
import { useState, useMemo } from "react";
9+
10+
// Helper function to sanitize HTML - only works on client side
11+
const sanitizeHTMLSync = (
12+
html: string,
13+
options?: { ALLOWED_TAGS: string[] }
14+
): string => {
15+
if (typeof window === "undefined") {
16+
return html;
17+
}
18+
19+
// Lazy load DOMPurify only when needed on client side
20+
// Using require to avoid SSR issues
21+
try {
22+
const DOMPurify = require("dompurify");
23+
// Handle both default export and named export
24+
const purify = DOMPurify.default || DOMPurify;
25+
if (options) {
26+
return purify.sanitize(html, options);
27+
}
28+
return purify.sanitize(html);
29+
} catch (error) {
30+
// Fallback if DOMPurify fails to load
31+
console.warn(
32+
"DOMPurify failed to load, returning unsanitized HTML:",
33+
error
34+
);
35+
return html;
36+
}
37+
};
938

1039
export default function ModuleDocPage() {
1140
const params = useParams();
1241
const moduleId = params?.moduleId as string;
1342
const [copied, setCopied] = useState(false);
14-
43+
1544
const currentModule = sheetModules.find((m) => m.id === moduleId);
1645

46+
const sanitizedDocContent = useMemo(() => {
47+
if (!currentModule?.docContent) return "";
48+
return sanitizeHTMLSync(currentModule.docContent);
49+
}, [currentModule?.docContent]);
50+
1751
const handleDownloadPDF = () => {
1852
if (!currentModule) return;
19-
53+
2054
const printWindow = window.open("", "_blank");
2155
if (!printWindow) return;
2256

57+
const sanitizedModuleName = sanitizeHTMLSync(currentModule.name, {
58+
ALLOWED_TAGS: [],
59+
});
60+
2361
const htmlContent = `
2462
<!DOCTYPE html>
2563
<html>
2664
<head>
27-
<title>${currentModule.name} - 30 days of Open Source sheet</title>
65+
<title>${sanitizedModuleName} - 30 days of Open Source sheet</title>
2866
<style>
2967
body {
3068
font-family: 'Courier New', monospace;
@@ -57,9 +95,9 @@ export default function ModuleDocPage() {
5795
</style>
5896
</head>
5997
<body>
60-
<h1>${currentModule.name}</h1>
98+
<h1>${sanitizedModuleName}</h1>
6199
<div class="content">
62-
${currentModule.docContent}
100+
${sanitizedDocContent}
63101
</div>
64102
</body>
65103
</html>
@@ -100,7 +138,9 @@ export default function ModuleDocPage() {
100138
<ArrowLeft className="h-4 w-4" />
101139
<span>Back to Sheet</span>
102140
</Link>
103-
<h1 className="text-3xl font-bold text-white mb-2">{currentModule.name}</h1>
141+
<h1 className="text-3xl font-bold text-white mb-2">
142+
{currentModule.name}
143+
</h1>
104144
</div>
105145

106146
<div className="bg-ox-content rounded-lg p-8 border border-ox-header text-center">
@@ -154,13 +194,16 @@ export default function ModuleDocPage() {
154194
</button>
155195
</div>
156196
</div>
157-
<h1 className="text-3xl font-bold text-white mb-2">{currentModule.name}</h1>
197+
<h1 className="text-3xl font-bold text-white mb-2">
198+
{currentModule.name}
199+
</h1>
158200
</div>
159201

160202
{/* Content */}
161203
<div className="bg-ox-content rounded-lg p-8 prose prose-invert max-w-none font-DMfont border border-ox-header">
162204
<div
163-
dangerouslySetInnerHTML={{ __html: currentModule.docContent }}
205+
// eslint-disable-next-line react/no-danger -- Safe: docContent is sanitized with DOMPurify before rendering
206+
dangerouslySetInnerHTML={{ __html: sanitizedDocContent }}
164207
className="text-white [&_h1]:text-2xl [&_h1]:font-bold [&_h1]:mb-4 [&_h1]:mt-6 [&_h1]:text-white [&_h2]:text-xl [&_h2]:font-semibold [&_h2]:mb-3 [&_h2]:mt-5 [&_h2]:text-white [&_p]:text-gray-300 [&_p]:mb-4 [&_p]:leading-relaxed [&_ul]:list-disc [&_ul]:ml-6 [&_ul]:mb-4 [&_ul]:text-gray-300 [&_li]:mb-2 [&_pre]:bg-ox-sidebar [&_pre]:p-4 [&_pre]:rounded [&_pre]:overflow-x-auto [&_pre]:mb-4 [&_pre]:font-DMfont [&_pre]:border [&_pre]:border-ox-header [&_code]:text-ox-purple [&_code]:bg-ox-sidebar [&_code]:px-1 [&_code]:py-0.5 [&_code]:rounded [&_code]:font-DMfont [&_img]:max-w-full [&_img]:h-auto [&_img]:rounded-lg [&_img]:my-5 [&_img]:border [&_img]:border-ox-header"
165208
/>
166209
</div>

apps/web/src/data/sheet/module-0.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ export const module0: SheetModule = {
8888
8989
<img src="/images/sheet-2.webp" alt="Ajeet's work screenshot" style="width: 60%; max-width: 60%; height: auto; border-radius: 8px; margin: 20px auto; display: block;" />
9090
91-
<p>also there are going to be couple of bonus modules or couple of ad hoc modules. so if i for forget something in this list then i will cover all of that in these bonue modules</p>
91+
<p>also there are going to be couple of bonus modules or couple of ad hoc modules. so if i for forget something in this list then i will cover all of that in these bonus modules</p>
9292
9393
<h3 style="margin-top: 40px; margin-bottom: 20px; color: #9455f4;">todos:</h3>
9494
<ul style="list-style: none; padding-left: 0;">

apps/web/src/data/sheet/module-10.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ export const module10: SheetModule = {
77
<h1>Live fix/implement the issue - 2</h1>
88
<p>Second live session on fixing and implementing an issue in an OPEN SOURCE project.</p>
99
`,
10-
videoUrl: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
10+
videoUrl: "",
1111
comingSoon: true,
1212
};

apps/web/src/data/sheet/module-11.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ export const module11: SheetModule = {
77
<h1>Live fix/implement the issue - 3</h1>
88
<p>Third live session on fixing and implementing an issue in an OPEN SOURCE project.</p>
99
`,
10-
videoUrl: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
10+
videoUrl: "",
1111
comingSoon: true,
1212
};

apps/web/src/data/sheet/module-12.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ export const module12: SheetModule = {
77
<h1>Live fix/implement the issue - 4</h1>
88
<p>Fourth live session on fixing and implementing an issue in an OPEN SOURCE project.</p>
99
`,
10-
videoUrl: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
10+
videoUrl: "",
1111
comingSoon: true,
1212
};

apps/web/src/data/sheet/module-13.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ export const module13: SheetModule = {
77
<h1>Raise PRs and fix the reviews</h1>
88
<p>Learn how to create pull requests and effectively address review comments from maintainers.</p>
99
`,
10-
videoUrl: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
10+
videoUrl: "",
1111
comingSoon: true,
1212
};

apps/web/src/data/sheet/module-14.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,6 @@ export const module14: SheetModule = {
77
<h1>How to tackle competition in saturated orgs?</h1>
88
<p>Strategies for standing out and getting your contributions noticed in highly competitive OPEN SOURCE organizations.</p>
99
`,
10-
videoUrl: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
10+
videoUrl: "",
1111
comingSoon: true,
1212
};

0 commit comments

Comments
 (0)