Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,7 @@ GOOGLE_APPLICATION_CREDENTIALS_JSON="" # The content of the JSON file you d

# Upstash_Redis
UPSTASH_REDIS_REST_URL="" # REST URL of your Upstash Redis database
UPSTASH_REDIS_REST_TOKEN="" # REST API token for Upstash Redis
UPSTASH_REDIS_REST_TOKEN="" # REST API token for Upstash Redis

NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN=""
NEXT_PUBLIC_POSTHOG_HOST="https://e.papers.codechefvit.com"
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@
"next-themes": "^0.3.0",
"pdf-lib": "^1.17.1",
"pdfjs-dist": "4.8.69",
"posthog-js": "^1.419.4",
"posthog-node": "^5.51.2",
"prettier": "^3.5.3",
"prettier-plugin-tailwindcss": "^0.6.11",
"raw-loader": "^4.0.2",
Expand Down
195 changes: 150 additions & 45 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

21 changes: 19 additions & 2 deletions src/app/api/request/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { success, failure } from "@/lib/utils/response";
import { createPaperRequest } from "@/lib/services/paper"
import type { CreatePaperInputType } from "@/lib/services/paper"
import { createPaperRequest } from "@/lib/services/paper";
import type { CreatePaperInputType } from "@/lib/services/paper";
import { getPostHogClient } from "@/lib/posthog-server";
import { randomUUID } from "crypto";

export async function POST(req: Request) {
try {
Expand All @@ -11,6 +13,21 @@ export async function POST(req: Request) {
}

const newRequest = await createPaperRequest({subject, exam, slot, year});

const posthog = getPostHogClient();
if (posthog) {
posthog.capture({
distinctId: randomUUID(),
event: "paper_request_created",
properties: {
exam,
slot,
year,
},
});
await posthog.flush();
}

return success({ message: "Paper request submitted successfully!", request: newRequest }, "Created", 201);
} catch (error) {
console.error("Error creating paper request:", error);
Expand Down
16 changes: 16 additions & 0 deletions src/app/api/upload/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import { success, failure } from "@/lib/utils/response";
import { uploadPaper } from "@/lib/services/upload";
import { getPostHogClient } from "@/lib/posthog-server";
import { randomUUID } from "crypto";

export const runtime = "nodejs";

Expand All @@ -21,6 +23,20 @@ export async function POST(req: Request) {
return failure(result.message, result.status);
}

const posthog = getPostHogClient();
if (posthog) {
posthog.capture({
distinctId: randomUUID(),
event: "paper_upload_completed",
properties: {
file_count: files.length,
is_pdf: isPdf,
campus: campus ?? "unknown",
},
});
await posthog.flush();
}

return success(
{ file_url: result.file_url, thumbnail_url: result.thumbnail_url },
"Created",
Expand Down
33 changes: 18 additions & 15 deletions src/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Navbar from "@/components/Navbar";
import Footer from "@/components/Footer";
import ChildrenWrapper from "@/components/ChildrenWrapper";
import { CoursesProvider } from "@/context/courseContext";
import { PostHogProvider } from "@/components/PostHogProvider";

export const metadata: Metadata = {
metadataBase: new URL("https://papers.codechefvit.com/"),
Expand Down Expand Up @@ -113,21 +114,23 @@ export default function RootLayout({
</Script>
</head>
<body>
<ThemeProvider
attribute="class"
defaultTheme="dark"
enableSystem
disableTransitionOnChange
>
<Toaster position="top-right" reverseOrder={false} />
<div className="bg-[#F3F5FF] dark:bg-[#070114]">
<CoursesProvider>
<Navbar />
<ChildrenWrapper>{children}</ChildrenWrapper>
<Footer />
</CoursesProvider>
</div>
</ThemeProvider>
<PostHogProvider>
<ThemeProvider
attribute="class"
defaultTheme="dark"
enableSystem
disableTransitionOnChange
>
<Toaster position="top-right" reverseOrder={false} />
<div className="bg-[#F3F5FF] dark:bg-[#070114]">
<CoursesProvider>
<Navbar />
<ChildrenWrapper>{children}</ChildrenWrapper>
<Footer />
</CoursesProvider>
</div>
</ThemeProvider>
</PostHogProvider>
</body>
</html>
);
Expand Down
5 changes: 5 additions & 0 deletions src/components/CatalogueContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import SearchBarChild from "./Searchbar/searchbar-child";
import Link from "next/link";
import { useCourses } from "@/context/courseContext";
import { FilterProvider, useFilters } from "@/context/filterContext";
import posthog from "posthog-js";
import EmptyState from "./ui/EmptyState";
import SelectionToolbar from "./SelectionToolbar";
import SortComponent from "./ui/sorting";
Expand Down Expand Up @@ -143,6 +144,10 @@ const CatalogueContentInner = ({ subject }: { subject: string | null }) => {
: saved.filter((s) => s !== subject);

localStorage.setItem("userSubjects", JSON.stringify(updated));

posthog.capture(current ? "subject_pinned" : "subject_unpinned", {
subject: subject?.split(" [")[0]?.trim() ?? subject,
});
};

// Fetch papers ONLY when subject changes (not when filters change!)
Expand Down
3 changes: 3 additions & 0 deletions src/components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import { Mail } from "lucide-react";
import toast from "react-hot-toast";
import type { ApiResponse } from '@/interface'
import posthog from "posthog-js";

export default function Footer() {
const [email, setEmail] = useState("");
Expand Down Expand Up @@ -42,6 +43,8 @@ export default function Footer() {
},
);

posthog.capture("newsletter_subscribed");

setEmail("");
};

Expand Down
29 changes: 29 additions & 0 deletions src/components/PostHogProvider.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"use client";

import posthog from "posthog-js";
import { PostHogProvider as PHProvider } from "posthog-js/react";
import { useEffect } from "react";

export function PostHogProvider({ children }: { children: React.ReactNode }) {
useEffect(() => {
const token = process.env.NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN;
const host = process.env.NEXT_PUBLIC_POSTHOG_HOST;
if (!token || !host) {
if (process.env.NODE_ENV !== "production") {
console.error(
"NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN and NEXT_PUBLIC_POSTHOG_HOST variables required by PostHog are missing or un-configured, this causes events to be silently missed. This error stops appearing once both variables are configured",
);
}
return;
}
posthog.init(token, {
api_host: host,
ui_host: "https://us.posthog.com",
defaults: "2026-01-30",
capture_exceptions: true,
debug: process.env.NODE_ENV === "development",
});
}, []);

return <PHProvider client={posthog}>{children}</PHProvider>;
}
8 changes: 7 additions & 1 deletion src/components/ReportTagModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ import { MultiSelect } from "@/components/multi-select";
import LabeledInput from "@/components/ui/LabeledInput";
import LabeledSelect from "@/components/ui/LabeledSelect";
import axios from "axios";
import toast from "react-hot-toast";
import toast from "react-hot-toast";
import posthog from "posthog-js";
import { type ApiResponse } from '@/interface'

type ReportResponse = ApiResponse<{ error?: string; message?: string }>;
Expand Down Expand Up @@ -236,6 +237,11 @@ if (reportedFields.length === 0 && comment.trim().length === 0) {
}
)
.then(() => {
posthog.capture("paper_reported", {
fields_reported: reportedFields.map((f) => f.field),
fields_reported_count: reportedFields.length,
has_comment: comment.trim().length > 0,
});
modalSetOpen(false);
setComment("");
setEmail("");
Expand Down
2 changes: 2 additions & 0 deletions src/components/ShareButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import { FaShare } from "react-icons/fa";
import QR from "./qr";
import { Button } from "./ui/button";
import { usePathname } from "next/navigation";
import posthog from "posthog-js";

interface ShareButtonProps {
isFullscreen: boolean;
Expand Down Expand Up @@ -63,6 +64,7 @@ export default function ShareButton({ isFullscreen, viewerRef }: ShareButtonProp
loading: "Copying link...",
error: "Error copying link",
});
posthog.capture("paper_shared", { method: "link_copied" });
}}
>
<p>Copy Link To Clipboard</p>
Expand Down
5 changes: 5 additions & 0 deletions src/components/newPdfViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import { ExportPluginPackage } from '@embedpdf/plugin-export/react';

import { Download, ZoomIn, ZoomOut, Maximize2, Minimize2, BookOpenText, X } from "lucide-react";
import posthog from "posthog-js";
import { memo, useCallback, useEffect, useMemo, useRef, useState } from "react";
import { downloadFile } from "../lib/utils/download";
import { Button } from "./ui/button";
Expand Down Expand Up @@ -478,6 +479,9 @@
paper_title: name,
paper_url: url,
});
posthog.capture("paper_downloaded", {
paper_name: name,
});
await downloadFile(url, `${name}.pdf`);
}, [url, name]);

Expand Down Expand Up @@ -508,6 +512,7 @@
const toggleReadingMode = useCallback(() => {
setIsReadingMode((r) => {
const next = !r;
posthog.capture("reading_mode_toggled", { enabled: next });
onReadingModeChange?.(next);
return next;
});
Expand Down
7 changes: 7 additions & 0 deletions src/context/filterContext.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
useSearchParams,
} from "next/navigation";
import { type IPaper, type Filters } from "@/interface";
import posthog from "posthog-js";
import JSZip from "jszip";
import { toast } from "react-hot-toast";
import { getSecureUrl, generateFileName } from "@/lib/utils/download";
Expand Down Expand Up @@ -208,6 +209,12 @@ export const FilterProvider: React.FC<FilterProviderProps> = ({
a.remove();
URL.revokeObjectURL(url);

posthog.capture("papers_bulk_downloaded", {
download_count: uniquePapers.length - failedCount,
total_selected: uniquePapers.length,
failed_count: failedCount,
});

if (failedCount > 0) {
toast.success(
`Downloaded ${uniquePapers.length - failedCount} of ${uniquePapers.length} papers (${failedCount} failed).`,
Expand Down
21 changes: 21 additions & 0 deletions src/lib/posthog-server.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { PostHog } from "posthog-node";

let posthogClient: PostHog | null = null;

export function getPostHogClient(): PostHog | null {
const token = process.env.NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN;
if (!token) {
if (process.env.NODE_ENV !== "production") {
console.error(
"NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN variable required by PostHog is missing or un-configured, this causes events to be silently missed. This error stops appearing once NEXT_PUBLIC_POSTHOG_PROJECT_TOKEN is configured",
);
}
return null;
}
posthogClient ??= new PostHog(token, {
host: process.env.NEXT_PUBLIC_POSTHOG_HOST,
flushAt: 1,
flushInterval: 0,
});
return posthogClient;
}
Loading