From ca196036baadbeffad6f8cf103575659d3ed902b Mon Sep 17 00:00:00 2001
From: ikramBagban <107988060+IkramBagban@users.noreply.github.com>
Date: Sat, 1 Mar 2025 16:01:07 +0530
Subject: [PATCH 1/8] feat: add endpoint for open statistics and daily metrics
---
apps/backend/index.ts | 101 +++++++++++++++++++++++++++++++++++++++++-
1 file changed, 100 insertions(+), 1 deletion(-)
diff --git a/apps/backend/index.ts b/apps/backend/index.ts
index f9da50b..af1a9e8 100644
--- a/apps/backend/index.ts
+++ b/apps/backend/index.ts
@@ -11,6 +11,11 @@ import { FalAIModel } from "./models/FalAIModel";
import cors from "cors";
import { authMiddleware } from "./middleware";
import dotenv from "dotenv";
+import {
+ ModelTrainingStatusEnum,
+ OutputImageStatusEnum,
+ TransactionStatus,
+} from "@prisma/client";
import paymentRoutes from "./routes/payment.routes";
import { router as webhookRouter } from "./routes/webhook.routes";
@@ -256,7 +261,7 @@ app.get("/image/bulk", authMiddleware, async (req, res) => {
});
});
-app.get("/models", authMiddleware, async (req, res) => {
+app.get("/models", async (req, res) => {
const models = await prismaClient.model.findMany({
where: {
OR: [{ userId: req.userId }, { open: true }],
@@ -354,6 +359,100 @@ app.post("/fal-ai/webhook/image", async (req, res) => {
});
});
+app.get("/open", async (req, res) => {
+ console.log("in open");
+ const userCount = await prismaClient.user.count();
+ const trainedModelCount = await prismaClient.model.count({
+ where: { trainingStatus: ModelTrainingStatusEnum.Generated },
+ });
+ const imagesCount = await prismaClient.outputImages.count({
+ where: { status: OutputImageStatusEnum.Generated },
+ });
+ const packCount = await prismaClient.packs.count();
+
+ const totalRevenue = await prismaClient.transaction.aggregate({
+ where: { status: TransactionStatus.SUCCESS },
+ _sum: { amount: true },
+ });
+
+ console.log("open stats", {
+ userCount,
+ trainedModelCount,
+ imagesCount,
+ packCount,
+ totalRevenue: totalRevenue._sum.amount,
+ });
+ const thirtyDaysAgo = new Date();
+ thirtyDaysAgo.setDate(thirtyDaysAgo.getDate() - 30);
+
+ const dailyUsers = await prismaClient.user.groupBy({
+ by: ["createdAt"],
+ where: { createdAt: { gte: thirtyDaysAgo } },
+ _count: { id: true },
+ orderBy: { createdAt: "asc" },
+ });
+
+ const dailyTrainedModels = await prismaClient.model.groupBy({
+ by: ["createdAt"],
+ where: {
+ createdAt: { gte: thirtyDaysAgo },
+ trainingStatus: ModelTrainingStatusEnum.Generated,
+ },
+ _count: { id: true },
+ orderBy: { createdAt: "asc" },
+ });
+
+ const dailyGeneratedImages = await prismaClient.outputImages.groupBy({
+ by: ["createdAt"],
+ where: {
+ status: OutputImageStatusEnum.Generated,
+ createdAt: { gte: thirtyDaysAgo },
+ },
+ _count: { id: true },
+ orderBy: { createdAt: "asc" },
+ });
+
+ const dailyRevenue = await prismaClient.transaction.groupBy({
+ by: ["createdAt"],
+ where: {
+ createdAt: { gte: thirtyDaysAgo },
+ status: TransactionStatus.SUCCESS,
+ },
+ _sum: { amount: true },
+ orderBy: { createdAt: "asc" },
+ });
+
+ const charts = {
+ dailyUsers: dailyUsers.map((user) => ({
+ date: user.createdAt,
+ count: user._count.id,
+ })),
+ dailyTrainedModels: dailyTrainedModels.map((model) => ({
+ date: model.createdAt,
+ count: model._count.id,
+ })),
+ dailyGeneratedImages: dailyGeneratedImages.map((image) => ({
+ date: image.createdAt,
+ count: image._count.id,
+ })),
+ dailyRevenue: dailyRevenue.map((entry) => ({
+ date: entry.createdAt,
+ amount: entry._sum.amount || 0,
+ })),
+ };
+
+ res.status(200).json({
+ data: {
+ userCount,
+ trainedModelCount,
+ imagesCount,
+ packCount,
+ totalRevenue: totalRevenue._sum.amount,
+ charts,
+ },
+ });
+});
+
app.use("/payment", paymentRoutes);
app.use("/api/webhook", webhookRouter);
From 92e0944f1d0dc9e6ca793b88a22d8155f6b004f7 Mon Sep 17 00:00:00 2001
From: ikramBagban <107988060+IkramBagban@users.noreply.github.com>
Date: Sat, 1 Mar 2025 16:01:35 +0530
Subject: [PATCH 2/8] feat: add database seeding script
---
packages/db/seed.ts | 168 ++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 168 insertions(+)
create mode 100644 packages/db/seed.ts
diff --git a/packages/db/seed.ts b/packages/db/seed.ts
new file mode 100644
index 0000000..b5fafa4
--- /dev/null
+++ b/packages/db/seed.ts
@@ -0,0 +1,168 @@
+import {
+ PrismaClient,
+ ModelTrainingStatusEnum,
+ OutputImageStatusEnum,
+ TransactionStatus,
+ ModelTypeEnum,
+ EthenecityEnum,
+ EyeColorEnum,
+ PlanType,
+} from "@prisma/client";
+import { subDays } from "date-fns";
+import { v4 as uuidv4 } from "uuid";
+
+const prisma = new PrismaClient();
+
+const modelTypes = [
+ ModelTypeEnum.Man,
+ ModelTypeEnum.Woman,
+ ModelTypeEnum.Others,
+];
+const ethnicities = [
+ EthenecityEnum.White,
+ EthenecityEnum.Black,
+ EthenecityEnum.Asian_American,
+ EthenecityEnum.East_Asian,
+ EthenecityEnum.South_East_Asian,
+ EthenecityEnum.South_Asian,
+ EthenecityEnum.Middle_Eastern,
+ EthenecityEnum.Pacific,
+ EthenecityEnum.Hispanic,
+];
+const eyeColors = [
+ EyeColorEnum.Brown,
+ EyeColorEnum.Blue,
+ EyeColorEnum.Hazel,
+ EyeColorEnum.Gray,
+];
+
+const dummyImage = (text: string) => {
+ return `https://dummyimage.com/100x100/3357ff/ffffff&text=${text}`;
+};
+async function seed() {
+ console.log("Starting database seeding...");
+
+ await prisma.transaction.deleteMany();
+ await prisma.userCredit.deleteMany();
+ await prisma.subscription.deleteMany();
+ await prisma.outputImages.deleteMany();
+ await prisma.packPrompts.deleteMany();
+ await prisma.model.deleteMany();
+ await prisma.packs.deleteMany();
+ await prisma.user.deleteMany();
+
+ const users = Array.from({ length: 19 }, (_, i) => ({
+ clerkId: `clerk_${uuidv4()}`,
+ createdAt: subDays(new Date(), Math.floor(Math.random() * 30)),
+ updatedAt: new Date(),
+ email: `user${i + 1}@example.com`,
+ name: `User ${i + 1}`,
+ profilePicture: dummyImage(`User ${i + 1}`),
+ }));
+ await prisma.user.createMany({ data: users });
+ console.log("Seeded 19 users");
+ const createdUsers = await prisma.user.findMany();
+
+ // -----------------------------------------------
+ const models = Array.from({ length: 15 }, (_, i) => ({
+ createdAt: subDays(new Date(), Math.floor(Math.random() * 30)),
+ updatedAt: new Date(),
+ trainingStatus: ModelTrainingStatusEnum.Generated,
+ name: `Model ${i + 1}`,
+ userId: createdUsers[Math.floor(Math.random() * createdUsers.length)].id,
+ type: modelTypes[Math.floor(Math.random() * modelTypes.length)],
+ age: Math.floor(Math.random() * 25) + 18,
+ ethinicity: ethnicities[Math.floor(Math.random() * ethnicities.length)],
+ eyeColor: eyeColors[Math.floor(Math.random() * eyeColors.length)],
+ bald: Math.random() > 0.5,
+ triggerWord: `trigger${i + 1}`,
+ tensorPath: `/tensor/model${i + 1}.pt`,
+ thumbnail: dummyImage(`Model ${i + 1}`),
+ falAiRequestId: `fal_${uuidv4()}`,
+ zipUrl: `https://example.com/zip${i + 1}.zip`,
+ open: Math.random() > 0.5,
+ }));
+ await prisma.model.createMany({ data: models });
+ console.log("Seeded 15 trained models");
+
+ const createdModels = await prisma.model.findMany();
+
+ // ======================
+ const images = Array.from({ length: 14 }, (_, i) => ({
+ createdAt: subDays(new Date(), Math.floor(Math.random() * 30)),
+ updatedAt: new Date(),
+ status: OutputImageStatusEnum.Generated,
+ imageUrl: dummyImage(`Image ${i + 1}`),
+ modelId: createdModels[Math.floor(Math.random() * createdModels.length)].id,
+ userId: createdUsers[Math.floor(Math.random() * createdUsers.length)].id,
+ prompt: `A photo of a person ${i + 1}`,
+ falAiRequestId: `fal_img_${uuidv4()}`,
+ }));
+ await prisma.outputImages.createMany({ data: images });
+ console.log("Seeded 14 generated images");
+
+ const packs = Array.from({ length: 12 }, (_, i) => ({
+ name: `Pack ${i + 1}`,
+ description: `Description for pack ${i + 1}`,
+ imageUrl1: dummyImage(`Pack ${i + 1}`),
+ imageUrl2: dummyImage(`Pack2 - ${i + 1}`),
+ }));
+ await prisma.packs.createMany({ data: packs });
+ console.log("Seeded 12 packs");
+ const createdPacks = await prisma.packs.findMany();
+
+
+
+ const packPrompts = Array.from({ length: 20 }, (_, i) => ({
+ prompt: `Prompt ${i + 1} for pack`,
+ packId: createdPacks[Math.floor(Math.random() * createdPacks.length)].id,
+ }));
+ await prisma.packPrompts.createMany({ data: packPrompts });
+ console.log("Seeded 20 pack prompts");
+
+ const plans = [PlanType.basic, PlanType.premium];
+ const subscriptions = Array.from({ length: 25 }, (_, i) => ({
+ userId: createdUsers[Math.floor(Math.random() * createdUsers.length)].id,
+ plan: plans[Math.floor(Math.random() * plans.length)],
+ paymentId: `pay_${uuidv4()}`,
+ orderId: `order_${uuidv4()}`,
+ createdAt: subDays(new Date(), Math.floor(Math.random() * 30)),
+ updatedAt: new Date(),
+ }));
+ await prisma.subscription.createMany({ data: subscriptions });
+ console.log("Seeded 25 subscriptions");
+
+ const userCredits = createdUsers.map((user) => ({
+ userId: user.id,
+ amount: Math.floor(Math.random() * 2500),
+ createdAt: subDays(new Date(), Math.floor(Math.random() * 30)),
+ updatedAt: new Date(),
+ }));
+ await prisma.userCredit.createMany({ data: userCredits });
+ console.log("Seeded 25 user credits");
+
+ const transactions = Array.from({ length: 10 }, (_, i) => ({
+ createdAt: subDays(new Date(), Math.floor(Math.random() * 30)),
+ updatedAt: new Date(),
+ status: TransactionStatus.SUCCESS,
+ amount: Math.random() < 0.5? 100 : 50 ,
+ userId: createdUsers[Math.floor(Math.random() * createdUsers.length)].id,
+ currency: "USD",
+ paymentId: `pay_${uuidv4()}`,
+ orderId: `order_${uuidv4()}`,
+ plan: plans[Math.floor(Math.random() * plans.length)],
+ }));
+ await prisma.transaction.createMany({ data: transactions });
+ console.log("Seeded 10 transactions");
+
+ console.log("Seeding completed!");
+}
+
+try {
+ await seed();
+} catch (e) {
+ console.error("Seeding failed:", e);
+ process.exit(1);
+} finally {
+ await prisma.$disconnect();
+}
From 4f85eae776fd31294eb8cdd875e5a4416e2fb2d5 Mon Sep 17 00:00:00 2001
From: ikramBagban <107988060+IkramBagban@users.noreply.github.com>
Date: Sat, 1 Mar 2025 17:37:50 +0530
Subject: [PATCH 3/8] feat: add Open page and add stats
---
apps/web/app/open/page.tsx | 25 ++++++++
apps/web/components/open/Header.tsx | 17 ++++++
apps/web/components/open/Loader.tsx | 15 +++++
apps/web/components/open/StatCards.tsx | 83 ++++++++++++++++++++++++++
apps/web/types/index.ts | 15 +++++
5 files changed, 155 insertions(+)
create mode 100644 apps/web/app/open/page.tsx
create mode 100644 apps/web/components/open/Header.tsx
create mode 100644 apps/web/components/open/Loader.tsx
create mode 100644 apps/web/components/open/StatCards.tsx
diff --git a/apps/web/app/open/page.tsx b/apps/web/app/open/page.tsx
new file mode 100644
index 0000000..784ab41
--- /dev/null
+++ b/apps/web/app/open/page.tsx
@@ -0,0 +1,25 @@
+import React from "react";
+import { BACKEND_URL } from "../config";
+import Header from "@/components/open/Header";
+import StatCards from "@/components/open/StatCards";
+
+async function OpenPage() {
+ console.log("BACKEND_URL", BACKEND_URL);
+ const response = await fetch(`${BACKEND_URL}/open`);
+ const statsData = await response.json();
+ console.log("statsData", statsData);
+
+ return (
+
+ );
+}
+
+export default OpenPage;
diff --git a/apps/web/components/open/Header.tsx b/apps/web/components/open/Header.tsx
new file mode 100644
index 0000000..1e8dd85
--- /dev/null
+++ b/apps/web/components/open/Header.tsx
@@ -0,0 +1,17 @@
+"use client";
+import React, { ReactNode } from "react";
+import { motion } from "framer-motion";
+const Header: React.FC<{ children: ReactNode }> = ({ children }) => {
+ return (
+
+ {children}
+
+ );
+};
+
+export default Header;
diff --git a/apps/web/components/open/Loader.tsx b/apps/web/components/open/Loader.tsx
new file mode 100644
index 0000000..5cf76c8
--- /dev/null
+++ b/apps/web/components/open/Loader.tsx
@@ -0,0 +1,15 @@
+import React from "react";
+import { motion } from "framer-motion";
+const Loader = () => {
+ return (
+
+
+
+ );
+};
+
+export default Loader;
diff --git a/apps/web/components/open/StatCards.tsx b/apps/web/components/open/StatCards.tsx
new file mode 100644
index 0000000..7febccd
--- /dev/null
+++ b/apps/web/components/open/StatCards.tsx
@@ -0,0 +1,83 @@
+"use client";
+
+import React from "react";
+import { motion } from "framer-motion";
+import { BarChart2, DollarSign, Image, Package, Users } from "lucide-react";
+
+const Card = ({ className, children }) => (
+ {children}
+);
+
+const CardHeader = ({ className, children }) => (
+ {children}
+);
+
+const CardTitle = ({ className, children }) => (
+ {children}
+);
+
+const CardContent = ({ className, children }) => (
+ {children}
+);
+
+interface StateProps {
+ stats: any;
+}
+
+const statVariants = {
+ hidden: { opacity: 0, y: 20 },
+ visible: (i: number) => ({
+ opacity: 1,
+ y: 0,
+ transition: { delay: i * 0.2, duration: 0.5, ease: "easeOut" },
+ }),
+};
+
+const StatCard: React.FC = ({ stats }) => {
+ const statsData = [
+ { title: "User Count", value: stats?.userCount, icon: Users },
+ {
+ title: "Models Trained",
+ value: stats.trainedModelCount,
+ icon: BarChart2,
+ },
+ {
+ title: "Images Generated ",
+ value: stats.imagesCount,
+ icon: Image,
+ },
+ {
+ title: "Pack Requests",
+ value: stats.packCount,
+ icon: Package,
+ },
+ {
+ title: "Total Revenue",
+ value: `$${stats.totalRevenue?.toLocaleString() || 0}`,
+ icon: DollarSign,
+ },
+ ];
+ return statsData.map((stat, index) => (
+
+
+
+
+
+ {stat.title}
+
+
+
+ {stat.value}
+
+
+
+ ));
+};
+
+export default StatCard;
diff --git a/apps/web/types/index.ts b/apps/web/types/index.ts
index b17afcd..9966eae 100644
--- a/apps/web/types/index.ts
+++ b/apps/web/types/index.ts
@@ -44,3 +44,18 @@ export interface Transaction {
createdAt: string;
updatedAt: string;
}
+
+export type StatsData = {
+ userCount: number;
+ trainedModelCount: number;
+ imagesCount: number;
+ packCount: number;
+ totalRevenue: number;
+ charts: {
+ dailyUsers: { date: string; count: number }[];
+ dailyTrainedModels: { date: string; count: number }[];
+ dailyGeneratedImages: { date: string; count: number }[];
+ dailyRevenue: { date: string; amount: number }[];
+ };
+};
+
From cafc242a25cd08c3efb616da6d66cab58b3b5698 Mon Sep 17 00:00:00 2001
From: ikramBagban <107988060+IkramBagban@users.noreply.github.com>
Date: Sat, 1 Mar 2025 17:42:21 +0530
Subject: [PATCH 4/8] feat: refactor Open page layout and enhance positions a
bit
---
apps/web/app/open/page.tsx | 4 +--
apps/web/components/open/StatCards.tsx | 46 ++++++++++++++------------
2 files changed, 26 insertions(+), 24 deletions(-)
diff --git a/apps/web/app/open/page.tsx b/apps/web/app/open/page.tsx
index 784ab41..131522e 100644
--- a/apps/web/app/open/page.tsx
+++ b/apps/web/app/open/page.tsx
@@ -11,11 +11,9 @@ async function OpenPage() {
return (
-
diff --git a/apps/web/components/open/StatCards.tsx b/apps/web/components/open/StatCards.tsx
index 7febccd..39e29d1 100644
--- a/apps/web/components/open/StatCards.tsx
+++ b/apps/web/components/open/StatCards.tsx
@@ -57,27 +57,31 @@ const StatCard: React.FC
= ({ stats }) => {
icon: DollarSign,
},
];
- return statsData.map((stat, index) => (
-
-
-
-
-
- {stat.title}
-
-
-
- {stat.value}
-
-
-
- ));
+ return (
+
+ {statsData.map((stat, index) => (
+
+
+
+
+
+ {stat.title}
+
+
+
+ {stat.value}
+
+
+
+ ))}
+
+ );
};
export default StatCard;
From 59074758b3bf2095986e0cca5537be47ffbab0c8 Mon Sep 17 00:00:00 2001
From: ikramBagban <107988060+IkramBagban@users.noreply.github.com>
Date: Sat, 1 Mar 2025 18:04:37 +0530
Subject: [PATCH 5/8] feat: add daily charts in open page
---
apps/web/app/open/page.tsx | 3 +
apps/web/components/open/OpenCardUI.tsx | 21 +++++
apps/web/components/open/OpenCharts.tsx | 113 ++++++++++++++++++++++++
apps/web/components/open/StatCards.tsx | 17 +---
apps/web/next.config.js | 5 ++
apps/web/package.json | 1 +
packages/db/package.json | 11 ++-
7 files changed, 153 insertions(+), 18 deletions(-)
create mode 100644 apps/web/components/open/OpenCardUI.tsx
create mode 100644 apps/web/components/open/OpenCharts.tsx
diff --git a/apps/web/app/open/page.tsx b/apps/web/app/open/page.tsx
index 131522e..3a1b724 100644
--- a/apps/web/app/open/page.tsx
+++ b/apps/web/app/open/page.tsx
@@ -2,6 +2,7 @@ import React from "react";
import { BACKEND_URL } from "../config";
import Header from "@/components/open/Header";
import StatCards from "@/components/open/StatCards";
+import OpenCharts from "@/components/open/OpenCharts";
async function OpenPage() {
console.log("BACKEND_URL", BACKEND_URL);
@@ -15,6 +16,8 @@ async function OpenPage() {
+
+
);
diff --git a/apps/web/components/open/OpenCardUI.tsx b/apps/web/components/open/OpenCardUI.tsx
new file mode 100644
index 0000000..6cb3c7b
--- /dev/null
+++ b/apps/web/components/open/OpenCardUI.tsx
@@ -0,0 +1,21 @@
+import React from "react";
+
+interface Props {
+ className? : string,
+ children: React.ReactNode
+}
+export const Card:React.FC = ({ className, children }) => (
+ {children}
+);
+
+export const CardHeader:React.FC = ({ className, children }) => (
+ {children}
+);
+
+export const CardTitle:React.FC = ({ className, children }) => (
+ {children}
+);
+
+export const CardContent:React.FC = ({ className, children }) => (
+ {children}
+);
diff --git a/apps/web/components/open/OpenCharts.tsx b/apps/web/components/open/OpenCharts.tsx
new file mode 100644
index 0000000..0e0a2e6
--- /dev/null
+++ b/apps/web/components/open/OpenCharts.tsx
@@ -0,0 +1,113 @@
+"use client";
+
+import { motion } from "framer-motion";
+import {
+ LineChart,
+ Line,
+ XAxis,
+ YAxis,
+ CartesianGrid,
+ Tooltip,
+ ResponsiveContainer,
+} from "recharts";
+import { format } from "date-fns";
+import { Card, CardContent, CardHeader, CardTitle } from "./OpenCardUI";
+import { useTheme } from "next-themes";
+
+const chartVariants = {
+ hidden: { opacity: 0, scale: 0.95 },
+ visible: {
+ opacity: 1,
+ scale: 1,
+ transition: { duration: 0.7, ease: "easeOut" },
+ },
+};
+
+const OpenCharts = ({ statsData }) => {
+ const { theme } = useTheme();
+ console.log("statsdata", statsData);
+
+ const chartConfig = [
+ {
+ title: "Daily Users",
+ data: statsData.charts?.dailyUsers,
+ key: "count",
+ color: "#3b82f6",
+ },
+ {
+ title: "Daily Trained Models",
+ data: statsData.charts?.dailyTrainedModels,
+ key: "count",
+ color: "#10b981",
+ },
+ {
+ title: "Daily Generated Images",
+ data: statsData.charts?.dailyGeneratedImages,
+ key: "count",
+ color: "#f59e0b",
+ },
+ {
+ title: "Daily Revenue",
+ data: statsData.charts?.dailyRevenue,
+ key: "amount",
+ color: "#ef4444",
+ },
+ ];
+ console.log("chartConfig", chartConfig);
+ return (
+
+ {(chartConfig || []).map((chart) => (
+
+
+
+
+ {chart.title}
+
+
+
+
+
+
+
+ format(new Date(date), "MMM dd")}
+ stroke={theme === "dark" ? "#ccc" : "#666"}
+ />
+
+
+
+
+
+
+
+
+
+ ))}
+
+ );
+};
+
+export default OpenCharts;
diff --git a/apps/web/components/open/StatCards.tsx b/apps/web/components/open/StatCards.tsx
index 39e29d1..b677015 100644
--- a/apps/web/components/open/StatCards.tsx
+++ b/apps/web/components/open/StatCards.tsx
@@ -3,22 +3,7 @@
import React from "react";
import { motion } from "framer-motion";
import { BarChart2, DollarSign, Image, Package, Users } from "lucide-react";
-
-const Card = ({ className, children }) => (
- {children}
-);
-
-const CardHeader = ({ className, children }) => (
- {children}
-);
-
-const CardTitle = ({ className, children }) => (
- {children}
-);
-
-const CardContent = ({ className, children }) => (
- {children}
-);
+import { Card, CardContent, CardHeader, CardTitle } from "./OpenCardUI";
interface StateProps {
stats: any;
diff --git a/apps/web/next.config.js b/apps/web/next.config.js
index 6dc56ab..0545e71 100644
--- a/apps/web/next.config.js
+++ b/apps/web/next.config.js
@@ -2,6 +2,11 @@
const nextConfig = {
images: {
remotePatterns: [
+
+ {
+ protocol: "https",
+ hostname: "dummyimage.com",
+ },
{
protocol: "https",
hostname: "r2-us-west.photoai.com",
diff --git a/apps/web/package.json b/apps/web/package.json
index 2469cc0..b7acf47 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -43,6 +43,7 @@
"react": "^19.0.0",
"react-dom": "^19.0.0",
"react-hot-toast": "^2.5.1",
+ "recharts": "^2.15.1",
"tailwind-merge": "^3.0.1",
"tailwindcss": "^4.0.6",
"tailwindcss-animate": "^1.0.7"
diff --git a/packages/db/package.json b/packages/db/package.json
index c8687a0..3511669 100644
--- a/packages/db/package.json
+++ b/packages/db/package.json
@@ -5,14 +5,21 @@
"exports": {
".": "./index.ts"
},
+ "scripts": {
+ "seed": "bun run seed.ts"
+ },
"devDependencies": {
"@types/bun": "latest",
- "prisma": "^6.3.1"
+ "prisma": "^6.3.1",
+ "ts-node": "^10.9.1"
+
},
"peerDependencies": {
"typescript": "^5.0.0"
},
"dependencies": {
- "@prisma/client": "6.3.1"
+ "@prisma/client": "6.3.1",
+ "date-fns": "^4.1.0",
+ "uuid": "^11.0.0"
}
}
\ No newline at end of file
From f8f7bdb016adb7d390fd4fd59a6647cbd9c5d1d3 Mon Sep 17 00:00:00 2001
From: ikramBagban <107988060+IkramBagban@users.noreply.github.com>
Date: Sat, 1 Mar 2025 18:30:27 +0530
Subject: [PATCH 6/8] refactor: add types & clean the code
---
apps/web/app/open/page.tsx | 16 ++-
.../open/{Header.tsx => Heading.tsx} | 9 +-
apps/web/components/open/OpenCharts.tsx | 121 +++++++++---------
apps/web/components/open/StatCards.tsx | 56 ++++----
4 files changed, 109 insertions(+), 93 deletions(-)
rename apps/web/components/open/{Header.tsx => Heading.tsx} (64%)
diff --git a/apps/web/app/open/page.tsx b/apps/web/app/open/page.tsx
index 3a1b724..a59903f 100644
--- a/apps/web/app/open/page.tsx
+++ b/apps/web/app/open/page.tsx
@@ -1,23 +1,27 @@
import React from "react";
import { BACKEND_URL } from "../config";
-import Header from "@/components/open/Header";
+import Heading from "@/components/open/Heading";
import StatCards from "@/components/open/StatCards";
import OpenCharts from "@/components/open/OpenCharts";
async function OpenPage() {
- console.log("BACKEND_URL", BACKEND_URL);
const response = await fetch(`${BACKEND_URL}/open`);
const statsData = await response.json();
- console.log("statsData", statsData);
return (
-
+
+ Open Stats
+
-
+
+
+
-
+
+
+
);
diff --git a/apps/web/components/open/Header.tsx b/apps/web/components/open/Heading.tsx
similarity index 64%
rename from apps/web/components/open/Header.tsx
rename to apps/web/components/open/Heading.tsx
index 1e8dd85..e134474 100644
--- a/apps/web/components/open/Header.tsx
+++ b/apps/web/components/open/Heading.tsx
@@ -1,17 +1,20 @@
"use client";
import React, { ReactNode } from "react";
import { motion } from "framer-motion";
-const Header: React.FC<{ children: ReactNode }> = ({ children }) => {
+const Heading: React.FC<{ children: ReactNode; className?: string }> = ({
+ children,
+ className
+}) => {
return (
{children}
);
};
-export default Header;
+export default Heading;
diff --git a/apps/web/components/open/OpenCharts.tsx b/apps/web/components/open/OpenCharts.tsx
index 0e0a2e6..986614d 100644
--- a/apps/web/components/open/OpenCharts.tsx
+++ b/apps/web/components/open/OpenCharts.tsx
@@ -13,6 +13,7 @@ import {
import { format } from "date-fns";
import { Card, CardContent, CardHeader, CardTitle } from "./OpenCardUI";
import { useTheme } from "next-themes";
+import { StatsData } from "@/types";
const chartVariants = {
hidden: { opacity: 0, scale: 0.95 },
@@ -23,11 +24,19 @@ const chartVariants = {
},
};
-const OpenCharts = ({ statsData }) => {
+interface CharConfig {
+ title: string;
+ data:
+ | { date: string }[]
+ | { date: string; count?: number; amount?: number }[];
+ key: "count" | "amount";
+ color: string;
+}
+
+const OpenCharts: React.FC<{ statsData: StatsData }> = ({ statsData }) => {
const { theme } = useTheme();
- console.log("statsdata", statsData);
- const chartConfig = [
+ const chartConfig: CharConfig[] = [
{
title: "Daily Users",
data: statsData.charts?.dailyUsers,
@@ -53,61 +62,57 @@ const OpenCharts = ({ statsData }) => {
color: "#ef4444",
},
];
- console.log("chartConfig", chartConfig);
- return (
-
- {(chartConfig || []).map((chart) => (
-
-
-
-
- {chart.title}
-
-
-
-
-
-
-
- format(new Date(date), "MMM dd")}
- stroke={theme === "dark" ? "#ccc" : "#666"}
- />
-
-
-
-
-
-
-
-
-
- ))}
-
- );
+
+ return (chartConfig || []).map((chart: CharConfig) => (
+
+
+
+
+ {chart.title}
+
+
+
+
+
+
+
+ format(new Date(date), "MMM dd")}
+ stroke={theme === "dark" ? "#ccc" : "#666"}
+ />
+
+
+
+
+
+
+
+
+
+ ));
};
export default OpenCharts;
diff --git a/apps/web/components/open/StatCards.tsx b/apps/web/components/open/StatCards.tsx
index b677015..ce1f675 100644
--- a/apps/web/components/open/StatCards.tsx
+++ b/apps/web/components/open/StatCards.tsx
@@ -4,9 +4,17 @@ import React from "react";
import { motion } from "framer-motion";
import { BarChart2, DollarSign, Image, Package, Users } from "lucide-react";
import { Card, CardContent, CardHeader, CardTitle } from "./OpenCardUI";
+import { StatsData } from "@/types";
interface StateProps {
- stats: any;
+ stats: Pick<
+ StatsData,
+ | "userCount"
+ | "trainedModelCount"
+ | "imagesCount"
+ | "packCount"
+ | "totalRevenue"
+ >;
}
const statVariants = {
@@ -42,31 +50,27 @@ const StatCard: React.FC = ({ stats }) => {
icon: DollarSign,
},
];
- return (
-
- {statsData.map((stat, index) => (
-
-
-
-
-
- {stat.title}
-
-
-
- {stat.value}
-
-
-
- ))}
-
- );
+ return statsData.map((stat, index) => (
+
+
+
+
+
+ {stat.title}
+
+
+
+ {stat.value}
+
+
+
+ ));
};
export default StatCard;
From 5c7caa0c2c78209da5ab635bc001df31c58ca22f Mon Sep 17 00:00:00 2001
From: ikramBagban <107988060+IkramBagban@users.noreply.github.com>
Date: Sun, 2 Mar 2025 15:29:13 +0530
Subject: [PATCH 7/8] feat: add incremental site regenration
---
apps/web/app/open/page.tsx | 10 ++++++++--
1 file changed, 8 insertions(+), 2 deletions(-)
diff --git a/apps/web/app/open/page.tsx b/apps/web/app/open/page.tsx
index a59903f..15a166b 100644
--- a/apps/web/app/open/page.tsx
+++ b/apps/web/app/open/page.tsx
@@ -4,9 +4,15 @@ import Heading from "@/components/open/Heading";
import StatCards from "@/components/open/StatCards";
import OpenCharts from "@/components/open/OpenCharts";
+async function getStatsData() {
+ const response = await fetch(`${BACKEND_URL}/open`, {
+ next: { revalidate: 3600 },
+ });
+ return response.json();
+}
+
async function OpenPage() {
- const response = await fetch(`${BACKEND_URL}/open`);
- const statsData = await response.json();
+ const statsData = await getStatsData();
return (
From 70657bed1a0366ec08ef8b170744325bffe06aa5 Mon Sep 17 00:00:00 2001
From: ikramBagban <107988060+IkramBagban@users.noreply.github.com>
Date: Sun, 16 Mar 2025 22:28:51 +0530
Subject: [PATCH 8/8] fix: revalidating stats data every 24h
---
apps/web/app/open/page.tsx | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/apps/web/app/open/page.tsx b/apps/web/app/open/page.tsx
index 15a166b..3c26f3a 100644
--- a/apps/web/app/open/page.tsx
+++ b/apps/web/app/open/page.tsx
@@ -6,7 +6,7 @@ import OpenCharts from "@/components/open/OpenCharts";
async function getStatsData() {
const response = await fetch(`${BACKEND_URL}/open`, {
- next: { revalidate: 3600 },
+ next: { revalidate: 60 * 60 * 24 },
});
return response.json();
}