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 ( +
+
+
Open Stats
+
+ +
+ +
+
+ ); +} + +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 (
-
+
Open Stats
-
-
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() {
Open Stats
+ +
); 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
+ + 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(); }