+ <>
+ {/* Backdrop overlay when sidebar is expanded on mobile */}
+ {isExpanded && isMobile && (
-
+ className="fixed inset-0 bg-gray-900 bg-opacity-50 z-10 transition-opacity duration-300"
+ onClick={onToggle}
+ />
+ )}
+
+
+
+ {/* Toggle button when sidebar is collapsed - above logo */}
+ {!isExpanded && (
+
+
+
+ )}
+
+ {/* Logo section with link to homepage */}
+
+
+
+
+ {isExpanded && (
+
+ LiveDash
+
+ )}
+
{isExpanded && (
-
LiveDash
+
+
+
)}
-
- {/* Toggle button */}
-
-
+
}
+ isExpanded={isExpanded}
+ isActive={pathname === "/dashboard"}
+ onNavigate={onNavigate}
+ />
+
+
+
+ }
+ isExpanded={isExpanded}
+ isActive={pathname === "/dashboard/overview"}
+ onNavigate={onNavigate}
+ />
+ }
+ isExpanded={isExpanded}
+ isActive={pathname.startsWith("/dashboard/sessions")}
+ onNavigate={onNavigate}
+ />
+ }
+ isExpanded={isExpanded}
+ isActive={pathname === "/dashboard/company"}
+ onNavigate={onNavigate}
+ />
+ }
+ isExpanded={isExpanded}
+ isActive={pathname === "/dashboard/users"}
+ onNavigate={onNavigate}
+ />
+
+
+
+
- {/* Navigation items */}
-
- {/* Logout at the bottom */}
-
-
-
-
+ >
);
}
diff --git a/components/WordCloud.tsx b/components/WordCloud.tsx
index c4efe8c..473fed9 100644
--- a/components/WordCloud.tsx
+++ b/components/WordCloud.tsx
@@ -2,15 +2,7 @@
import { useRef, useEffect, useState } from "react";
import { select } from "d3-selection";
-import cloud from "d3-cloud";
-
-interface CloudWord {
- text: string;
- size: number;
- x?: number;
- y?: number;
- rotate?: number;
-}
+import cloud, { Word } from "d3-cloud";
interface WordCloudProps {
words: {
@@ -53,12 +45,12 @@ export default function WordCloud({
)
.padding(5)
.rotate(() => (~~(Math.random() * 6) - 3) * 15) // Rotate between -45 and 45 degrees
- .fontSize((d: CloudWord) => d.size)
+ .fontSize((d: Word) => d.size || 10)
.on("end", draw);
layout.start();
- function draw(words: CloudWord[]) {
+ function draw(words: Word[]) {
svg
.append("g")
.attr("transform", `translate(${width / 2},${height / 2})`)
@@ -66,7 +58,7 @@ export default function WordCloud({
.data(words)
.enter()
.append("text")
- .style("font-size", (d: CloudWord) => `${d.size}px`)
+ .style("font-size", (d: Word) => `${d.size || 10}px`)
.style("font-family", "Inter, Arial, sans-serif")
.style("fill", () => {
// Create a nice gradient of colors
@@ -85,10 +77,10 @@ export default function WordCloud({
.attr("text-anchor", "middle")
.attr(
"transform",
- (d: CloudWord) =>
+ (d: Word) =>
`translate(${d.x || 0},${d.y || 0}) rotate(${d.rotate || 0})`
)
- .text((d: CloudWord) => d.text);
+ .text((d: Word) => d.text || "");
}
// Cleanup function
diff --git a/lib/metrics.ts b/lib/metrics.ts
index 15d8334..e06fca9 100644
--- a/lib/metrics.ts
+++ b/lib/metrics.ts
@@ -373,19 +373,22 @@ export function sessionMetrics(
// If times are identical, duration will be 0.
// If endTime is before startTime, this still yields a positive duration representing the magnitude of the difference.
const duration = Math.abs(timeDifference);
+ // console.log(
+ // `[metrics] duration is ${duration} for session ${session.id || session.sessionId}`
+ // );
totalSessionDuration += duration; // Add this duration
if (timeDifference < 0) {
// Log a specific warning if the original endTime was before startTime
console.warn(
- `[metrics] endTime (${session.endTime}) was before startTime (${session.startTime}) for session ${session.id || session.sessionId}. Using absolute difference as duration (${(duration / (1000 * 60)).toFixed(2)} mins).`
+ `[metrics] endTime (${session.endTime}) was before startTime (${session.startTime}) for session ${session.id || session.sessionId}. Using absolute difference as duration (${(duration / 1000).toFixed(2)} seconds).`
);
} else if (timeDifference === 0) {
- // Optionally, log if times are identical, though this might be verbose if common
- console.log(
- `[metrics] startTime and endTime are identical for session ${session.id || session.sessionId}. Duration is 0.`
- );
+ // // Optionally, log if times are identical, though this might be verbose if common
+ // console.log(
+ // `[metrics] startTime and endTime are identical for session ${session.id || session.sessionId}. Duration is 0.`
+ // );
}
// If timeDifference > 0, it's a normal positive duration, no special logging needed here for that case.
@@ -399,7 +402,9 @@ export function sessionMetrics(
}
if (!session.endTime) {
// This is a common case for ongoing sessions, might not always be an error
- // console.log(`[metrics] Missing endTime for session ${session.id || session.sessionId} - likely ongoing or data issue.`);
+ console.log(
+ `[metrics] Missing endTime for session ${session.id || session.sessionId} - likely ongoing or data issue.`
+ );
}
}
@@ -493,7 +498,7 @@ export function sessionMetrics(
const uniqueUsers = uniqueUserIds.size;
const avgSessionLength =
validSessionsForDuration > 0
- ? totalSessionDuration / validSessionsForDuration / 1000 / 60 // Convert ms to minutes
+ ? totalSessionDuration / validSessionsForDuration / 1000 // Convert ms to minutes
: 0;
const avgResponseTime =
validSessionsForResponseTime > 0
@@ -510,6 +515,12 @@ export function sessionMetrics(
const avgSessionsPerDay =
numDaysWithSessions > 0 ? totalSessions / numDaysWithSessions : 0;
+ // console.log("Debug metrics calculation:", {
+ // totalSessionDuration,
+ // validSessionsForDuration,
+ // calculatedAvgSessionLength: avgSessionLength,
+ // });
+
return {
totalSessions,
uniqueUsers,
diff --git a/lib/types.ts b/lib/types.ts
index de4ca14..383b8ec 100644
--- a/lib/types.ts
+++ b/lib/types.ts
@@ -138,6 +138,10 @@ export interface MetricsResult {
usersTrend?: number; // e.g., percentage change in uniqueUsers
avgSessionTimeTrend?: number; // e.g., percentage change in avgSessionLength
avgResponseTimeTrend?: number; // e.g., percentage change in avgResponseTime
+
+ // Debug properties
+ totalSessionDuration?: number;
+ validSessionsForDuration?: number;
}
export interface ApiResponse
{
diff --git a/pages/api/forgot-password.ts b/pages/api/forgot-password.ts
index 3f36fca..fab8aca 100644
--- a/pages/api/forgot-password.ts
+++ b/pages/api/forgot-password.ts
@@ -1,27 +1,20 @@
import { prisma } from "../../lib/prisma";
import { sendEmail } from "../../lib/sendEmail";
import crypto from "crypto";
-import type { IncomingMessage, ServerResponse } from "http";
-
-type NextApiRequest = IncomingMessage & {
- body: {
- email: string;
- [key: string]: unknown;
- };
-};
-
-type NextApiResponse = ServerResponse & {
- status: (code: number) => NextApiResponse;
- json: (data: Record) => void;
- end: () => void;
-};
+import type { NextApiRequest, NextApiResponse } from "next";
export default async function handler(
req: NextApiRequest,
res: NextApiResponse
) {
- if (req.method !== "POST") return res.status(405).end();
- const { email } = req.body;
+ if (req.method !== "POST") {
+ res.setHeader("Allow", ["POST"]);
+ return res.status(405).end(`Method ${req.method} Not Allowed`);
+ }
+
+ // Type the body with a type assertion
+ const { email } = req.body as { email: string };
+
const user = await prisma.user.findUnique({ where: { email } });
if (!user) return res.status(200).end(); // always 200 for privacy
diff --git a/pages/api/reset-password.ts b/pages/api/reset-password.ts
index cf77dd6..86bd4dd 100644
--- a/pages/api/reset-password.ts
+++ b/pages/api/reset-password.ts
@@ -34,12 +34,9 @@ export default async function handler(
});
if (!user) {
- return res
- .status(400)
- .json({
- error:
- "Invalid or expired token. Please request a new password reset.",
- });
+ return res.status(400).json({
+ error: "Invalid or expired token. Please request a new password reset.",
+ });
}
const hash = await bcrypt.hash(password, 10);
@@ -59,10 +56,8 @@ export default async function handler(
} catch (error) {
console.error("Reset password error:", error); // Log the error for server-side debugging
// Provide a generic error message to the client
- return res
- .status(500)
- .json({
- error: "An internal server error occurred. Please try again later.",
- });
+ return res.status(500).json({
+ error: "An internal server error occurred. Please try again later.",
+ });
}
}