├── src ├── app │ ├── favicon.ico │ ├── api │ │ └── test-db │ │ │ └── route.ts │ ├── dashboard │ │ ├── layout.tsx │ │ └── page.tsx │ ├── layout.tsx │ ├── not-found.tsx │ ├── sign-in │ │ └── [[...sign-in]] │ │ │ └── page.tsx │ ├── sign-up │ │ └── [[...sign-up]] │ │ │ └── page.tsx │ ├── globals.css │ ├── projects │ │ └── page.tsx │ ├── analytics │ │ └── page.tsx │ ├── settings │ │ └── page.tsx │ └── page.tsx ├── lib │ ├── utils.ts │ └── dbConnect.ts ├── components │ ├── theme-provider.tsx │ ├── ui │ │ ├── label.tsx │ │ ├── input.tsx │ │ ├── switch.tsx │ │ ├── avatar.tsx │ │ ├── tabs.tsx │ │ ├── button.tsx │ │ ├── card.tsx │ │ └── sheet.tsx │ ├── theme-toggle.tsx │ ├── sidebar.tsx │ ├── header.tsx │ └── charts │ │ ├── bar-chart.tsx │ │ ├── pie-chart.tsx │ │ ├── line-chart.tsx │ │ └── area-chart.tsx └── middleware.ts ├── postcss.config.mjs ├── public ├── vercel.svg ├── window.svg ├── file.svg ├── globe.svg └── next.svg ├── next.config.ts ├── eslint.config.mjs ├── components.json ├── tsconfig.json ├── .gitignore ├── package.json ├── tailwind.config.js ├── PLAN.md └── README.md /src/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hoodini/ncm-bp/HEAD/src/app/favicon.ico -------------------------------------------------------------------------------- /postcss.config.mjs: -------------------------------------------------------------------------------- 1 | const config = { 2 | plugins: ["@tailwindcss/postcss"], 3 | }; 4 | 5 | export default config; 6 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/lib/utils.ts: -------------------------------------------------------------------------------- 1 | import { clsx, type ClassValue } from "clsx" 2 | import { twMerge } from "tailwind-merge" 3 | 4 | export function cn(...inputs: ClassValue[]) { 5 | return twMerge(clsx(inputs)) 6 | } 7 | -------------------------------------------------------------------------------- /next.config.ts: -------------------------------------------------------------------------------- 1 | import type { NextConfig } from "next"; 2 | 3 | const nextConfig: NextConfig = { 4 | /* config options here */ 5 | images: { 6 | domains: ["picsum.photos"], 7 | }, 8 | }; 9 | 10 | export default nextConfig; 11 | -------------------------------------------------------------------------------- /src/components/theme-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import { ThemeProvider as NextThemesProvider } from "next-themes" 5 | import { type ThemeProviderProps } from "next-themes/dist/types" 6 | 7 | export function ThemeProvider({ children, ...props }: ThemeProviderProps) { 8 | return {children} 9 | } -------------------------------------------------------------------------------- /public/window.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/file.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | import { dirname } from "path"; 2 | import { fileURLToPath } from "url"; 3 | import { FlatCompat } from "@eslint/eslintrc"; 4 | 5 | const __filename = fileURLToPath(import.meta.url); 6 | const __dirname = dirname(__filename); 7 | 8 | const compat = new FlatCompat({ 9 | baseDirectory: __dirname, 10 | }); 11 | 12 | const eslintConfig = [ 13 | ...compat.extends("next/core-web-vitals", "next/typescript"), 14 | ]; 15 | 16 | export default eslintConfig; 17 | -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "new-york", 4 | "rsc": true, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "", 8 | "css": "src/app/globals.css", 9 | "baseColor": "neutral", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | }, 20 | "iconLibrary": "lucide" 21 | } -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2017", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "noEmit": true, 9 | "esModuleInterop": true, 10 | "module": "esnext", 11 | "moduleResolution": "bundler", 12 | "resolveJsonModule": true, 13 | "isolatedModules": true, 14 | "jsx": "preserve", 15 | "incremental": true, 16 | "plugins": [ 17 | { 18 | "name": "next" 19 | } 20 | ], 21 | "paths": { 22 | "@/*": ["./src/*"] 23 | } 24 | }, 25 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], 26 | "exclude": ["node_modules"] 27 | } 28 | -------------------------------------------------------------------------------- /src/components/ui/label.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as LabelPrimitive from "@radix-ui/react-label" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | function Label({ 9 | className, 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 21 | ) 22 | } 23 | 24 | export { Label } 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.* 7 | .yarn/* 8 | !.yarn/patches 9 | !.yarn/plugins 10 | !.yarn/releases 11 | !.yarn/versions 12 | 13 | # testing 14 | /coverage 15 | 16 | # next.js 17 | /.next/ 18 | /out/ 19 | 20 | # production 21 | /build 22 | 23 | # misc 24 | .DS_Store 25 | *.pem 26 | 27 | # debug 28 | npm-debug.log* 29 | yarn-debug.log* 30 | yarn-error.log* 31 | .pnpm-debug.log* 32 | 33 | # env files (can opt-in for committing if needed) 34 | .env* 35 | 36 | # vercel 37 | .vercel 38 | 39 | # typescript 40 | *.tsbuildinfo 41 | next-env.d.ts 42 | 43 | # clerk configuration (can include secrets) 44 | /.clerk/ 45 | -------------------------------------------------------------------------------- /src/middleware.ts: -------------------------------------------------------------------------------- 1 | import { clerkMiddleware, createRouteMatcher } from '@clerk/nextjs/server'; 2 | import { NextResponse } from 'next/server'; 3 | 4 | // Create a matcher for public routes that don't require authentication 5 | const isPublicRoute = createRouteMatcher([ 6 | '/sign-in(.*)', 7 | '/sign-up(.*)', 8 | '/api/test-db(.*)' 9 | ]); 10 | 11 | export default clerkMiddleware((auth, req) => { 12 | // If the route is not public and the user is not signed in, protect it 13 | if (!isPublicRoute(req)) { 14 | // This uses the correct auth.protect() approach 15 | // The middleware will handle redirecting to sign-in or returning 401 for API routes 16 | auth.protect(); 17 | } 18 | 19 | return NextResponse.next(); 20 | }); 21 | 22 | export const config = { 23 | matcher: [ 24 | '/((?!.+\\.[\\w]+$|_next).*)', 25 | '/', 26 | '/(api|trpc)(.*)' 27 | ], 28 | }; -------------------------------------------------------------------------------- /public/globe.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/ui/input.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | function Input({ className, type, ...props }: React.ComponentProps<"input">) { 6 | return ( 7 | 18 | ) 19 | } 20 | 21 | export { Input } 22 | -------------------------------------------------------------------------------- /src/app/api/test-db/route.ts: -------------------------------------------------------------------------------- 1 | import { NextResponse } from 'next/server'; 2 | import dbConnect from '@/lib/dbConnect'; 3 | 4 | export async function GET() { 5 | try { 6 | // Connect to the database 7 | const mongoose = await dbConnect(); 8 | 9 | // Get database stats 10 | const dbStats = { 11 | isConnected: mongoose.connection.readyState === 1, 12 | host: mongoose.connection.host, 13 | name: mongoose.connection.name, 14 | collections: mongoose.connection.readyState === 1 && mongoose.connection.db 15 | ? await mongoose.connection.db.listCollections().toArray() 16 | : [] 17 | }; 18 | 19 | // Return database information 20 | return NextResponse.json({ 21 | success: true, 22 | message: 'Connected to MongoDB Atlas successfully!', 23 | dbStats 24 | }); 25 | } catch (error) { 26 | console.error('API route error:', error); 27 | return NextResponse.json( 28 | { success: false, message: 'Failed to connect to MongoDB', error: String(error) }, 29 | { status: 500 } 30 | ); 31 | } 32 | } -------------------------------------------------------------------------------- /src/app/dashboard/layout.tsx: -------------------------------------------------------------------------------- 1 | import { Sidebar } from "@/components/sidebar" 2 | import { Header } from "@/components/header" 3 | import { Toaster } from "sonner" 4 | 5 | export default function DashboardLayout({ 6 | children, 7 | }: Readonly<{ 8 | children: React.ReactNode 9 | }>) { 10 | return ( 11 |
12 | 13 |
14 |
15 |
16 | {children} 17 |
18 |
19 |
20 |
21 | Created with 💜 by Yuval Avidani - AI Builder & Speaker 22 |
23 |
24 | "Fly High With YUV.AI" 25 |
26 |
27 |
28 |
29 | 30 |
31 | ) 32 | } -------------------------------------------------------------------------------- /src/components/ui/switch.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SwitchPrimitive from "@radix-ui/react-switch" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | function Switch({ 9 | className, 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 21 | 27 | 28 | ) 29 | } 30 | 31 | export { Switch } 32 | -------------------------------------------------------------------------------- /public/next.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ncm-bp", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev --turbopack", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@clerk/nextjs": "^6.18.0", 13 | "@radix-ui/react-avatar": "^1.1.7", 14 | "@radix-ui/react-dialog": "^1.1.11", 15 | "@radix-ui/react-label": "^2.1.4", 16 | "@radix-ui/react-slot": "^1.2.0", 17 | "@radix-ui/react-switch": "^1.2.2", 18 | "@radix-ui/react-tabs": "^1.1.9", 19 | "class-variance-authority": "^0.7.1", 20 | "clsx": "^2.1.1", 21 | "lucide-react": "^0.503.0", 22 | "mcp-mongodb-atlas": "^0.1.10", 23 | "mongodb": "^6.16.0", 24 | "mongoose": "^8.14.0", 25 | "next": "15.3.1", 26 | "next-themes": "^0.4.6", 27 | "react": "^19.0.0", 28 | "react-dom": "^19.0.0", 29 | "recharts": "^2.15.3", 30 | "sonner": "^2.0.3", 31 | "tailwind-merge": "^3.2.0", 32 | "tailwindcss-animate": "^1.0.7" 33 | }, 34 | "devDependencies": { 35 | "@eslint/eslintrc": "^3", 36 | "@tailwindcss/postcss": "^4", 37 | "@types/node": "^20", 38 | "@types/react": "^19", 39 | "@types/react-dom": "^19", 40 | "eslint": "^9", 41 | "eslint-config-next": "15.3.1", 42 | "tailwindcss": "^4", 43 | "tw-animate-css": "^1.2.8", 44 | "typescript": "^5" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/components/theme-toggle.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import { Moon, Sun } from "lucide-react" 5 | import { useTheme } from "next-themes" 6 | 7 | import { Button } from "@/components/ui/button" 8 | 9 | export function ThemeToggle() { 10 | const { resolvedTheme, setTheme } = useTheme() 11 | const [mounted, setMounted] = React.useState(false) 12 | 13 | // After mounting, we have access to the theme 14 | React.useEffect(() => { 15 | setMounted(true) 16 | }, []) 17 | 18 | if (!mounted) { 19 | return ( 20 | 27 | ) 28 | } 29 | 30 | return ( 31 | 41 | ) 42 | } -------------------------------------------------------------------------------- /src/components/ui/avatar.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as AvatarPrimitive from "@radix-ui/react-avatar" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | const Avatar = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | )) 21 | Avatar.displayName = AvatarPrimitive.Root.displayName 22 | 23 | const AvatarImage = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, ...props }, ref) => ( 27 | 32 | )) 33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName 34 | 35 | const AvatarFallback = React.forwardRef< 36 | React.ElementRef, 37 | React.ComponentPropsWithoutRef 38 | >(({ className, ...props }, ref) => ( 39 | 47 | )) 48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName 49 | 50 | export { Avatar, AvatarImage, AvatarFallback } -------------------------------------------------------------------------------- /src/lib/dbConnect.ts: -------------------------------------------------------------------------------- 1 | import mongoose from 'mongoose'; 2 | 3 | // Define the interface for the global mongoose cache 4 | interface MongooseCache { 5 | conn: typeof mongoose | null; 6 | promise: Promise | null; 7 | } 8 | 9 | // Declare mongoose property on global object 10 | declare global { 11 | var mongoose: MongooseCache | undefined; 12 | } 13 | 14 | const MONGODB_URI = process.env.MONGODB_URI as string; 15 | 16 | if (!MONGODB_URI) { 17 | throw new Error('Please define the MONGODB_URI environment variable'); 18 | } 19 | 20 | let cached = global.mongoose || { conn: null, promise: null }; 21 | 22 | // Set the mongoose cache on the global object 23 | global.mongoose = cached; 24 | 25 | async function dbConnect() { 26 | if (cached.conn) { 27 | return cached.conn; 28 | } 29 | 30 | if (!cached.promise) { 31 | const opts = { 32 | bufferCommands: false, 33 | }; 34 | 35 | cached.promise = mongoose.connect(MONGODB_URI, opts).then((mongoose) => { 36 | console.log('Connected to MongoDB Atlas!'); 37 | // Log information about the database 38 | const connectionState = mongoose.connection.readyState; 39 | const stateMap = { 40 | 0: 'disconnected', 41 | 1: 'connected', 42 | 2: 'connecting', 43 | 3: 'disconnecting', 44 | }; 45 | console.log(`MongoDB connection state: ${stateMap[connectionState as 0 | 1 | 2 | 3]}`); 46 | console.log(`MongoDB host: ${mongoose.connection.host}`); 47 | console.log(`MongoDB database name: ${mongoose.connection.name}`); 48 | 49 | return mongoose; 50 | }).catch(err => { 51 | console.error('MongoDB connection error:', err); 52 | throw err; 53 | }); 54 | } 55 | 56 | cached.conn = await cached.promise; 57 | return cached.conn; 58 | } 59 | 60 | export default dbConnect; 61 | -------------------------------------------------------------------------------- /src/app/layout.tsx: -------------------------------------------------------------------------------- 1 | import type { Metadata } from 'next' 2 | import { Outfit } from 'next/font/google' 3 | import { ClerkProvider } from '@clerk/nextjs' 4 | import { ThemeProvider } from '@/components/theme-provider' 5 | import { Header } from '@/components/header' 6 | import './globals.css' 7 | 8 | const outfit = Outfit({ 9 | subsets: ['latin'], 10 | variable: '--font-outfit', 11 | display: 'swap', 12 | weight: ['100', '200', '300', '400', '500', '600', '700', '800', '900'], 13 | }) 14 | 15 | export const metadata: Metadata = { 16 | title: 'NCM Business Portfolio', 17 | description: 'Developed by Yuval Avidani, AI Builder & Speaker', 18 | } 19 | 20 | export default function RootLayout({ 21 | children, 22 | }: Readonly<{ 23 | children: React.ReactNode 24 | }>) { 25 | return ( 26 | 27 | 28 | 29 |
30 |
31 |
32 |
33 |
34 | 40 |
41 |
42 | {children} 43 |
44 | 45 | 46 | 47 | 48 | ) 49 | } -------------------------------------------------------------------------------- /src/components/ui/tabs.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as TabsPrimitive from "@radix-ui/react-tabs" 5 | 6 | import { cn } from "@/lib/utils" 7 | 8 | function Tabs({ 9 | className, 10 | ...props 11 | }: React.ComponentProps) { 12 | return ( 13 | 18 | ) 19 | } 20 | 21 | function TabsList({ 22 | className, 23 | ...props 24 | }: React.ComponentProps) { 25 | return ( 26 | 34 | ) 35 | } 36 | 37 | function TabsTrigger({ 38 | className, 39 | ...props 40 | }: React.ComponentProps) { 41 | return ( 42 | 50 | ) 51 | } 52 | 53 | function TabsContent({ 54 | className, 55 | ...props 56 | }: React.ComponentProps) { 57 | return ( 58 | 63 | ) 64 | } 65 | 66 | export { Tabs, TabsList, TabsTrigger, TabsContent } 67 | -------------------------------------------------------------------------------- /src/components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { cva, type VariantProps } from "class-variance-authority" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const buttonVariants = cva( 8 | "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive", 9 | { 10 | variants: { 11 | variant: { 12 | default: 13 | "bg-primary text-primary-foreground shadow-xs hover:bg-primary/90", 14 | destructive: 15 | "bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60", 16 | outline: 17 | "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50", 18 | secondary: 19 | "bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80", 20 | ghost: 21 | "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50", 22 | link: "text-primary underline-offset-4 hover:underline", 23 | }, 24 | size: { 25 | default: "h-9 px-4 py-2 has-[>svg]:px-3", 26 | sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5", 27 | lg: "h-10 rounded-md px-6 has-[>svg]:px-4", 28 | icon: "size-9", 29 | }, 30 | }, 31 | defaultVariants: { 32 | variant: "default", 33 | size: "default", 34 | }, 35 | } 36 | ) 37 | 38 | function Button({ 39 | className, 40 | variant, 41 | size, 42 | asChild = false, 43 | ...props 44 | }: React.ComponentProps<"button"> & 45 | VariantProps & { 46 | asChild?: boolean 47 | }) { 48 | const Comp = asChild ? Slot : "button" 49 | 50 | return ( 51 | 56 | ) 57 | } 58 | 59 | export { Button, buttonVariants } 60 | -------------------------------------------------------------------------------- /src/components/ui/card.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | 3 | import { cn } from "@/lib/utils" 4 | 5 | function Card({ className, ...props }: React.ComponentProps<"div">) { 6 | return ( 7 |
15 | ) 16 | } 17 | 18 | function CardHeader({ className, ...props }: React.ComponentProps<"div">) { 19 | return ( 20 |
28 | ) 29 | } 30 | 31 | function CardTitle({ className, ...props }: React.ComponentProps<"div">) { 32 | return ( 33 |
38 | ) 39 | } 40 | 41 | function CardDescription({ className, ...props }: React.ComponentProps<"div">) { 42 | return ( 43 |
48 | ) 49 | } 50 | 51 | function CardAction({ className, ...props }: React.ComponentProps<"div">) { 52 | return ( 53 |
61 | ) 62 | } 63 | 64 | function CardContent({ className, ...props }: React.ComponentProps<"div">) { 65 | return ( 66 |
71 | ) 72 | } 73 | 74 | function CardFooter({ className, ...props }: React.ComponentProps<"div">) { 75 | return ( 76 |
81 | ) 82 | } 83 | 84 | export { 85 | Card, 86 | CardHeader, 87 | CardFooter, 88 | CardTitle, 89 | CardAction, 90 | CardDescription, 91 | CardContent, 92 | } 93 | -------------------------------------------------------------------------------- /src/app/not-found.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { Button } from "@/components/ui/button"; 3 | import { Home, ArrowLeft } from "lucide-react"; 4 | 5 | export default function NotFound() { 6 | return ( 7 |
8 | {/* Background gradient elements */} 9 |
10 |
11 |
12 |
13 |
14 | 15 | {/* Content with glass effect */} 16 |
17 |
18 |
19 | 404 20 |
21 | 22 |
23 |

24 | Page Not Found 25 |

26 |

27 | The page you're looking for doesn't exist or has been moved. 28 |

29 |
30 | 31 |
32 | 38 | 44 |
45 | 46 |
47 |

48 | "Fly High With YUV.AI" 49 |

50 |
51 |
52 |
53 |
54 | ); 55 | } -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | darkMode: ["class"], 4 | content: [ 5 | './pages/**/*.{ts,tsx}', 6 | './components/**/*.{ts,tsx}', 7 | './app/**/*.{ts,tsx}', 8 | './src/**/*.{ts,tsx}', 9 | ], 10 | prefix: "", 11 | theme: { 12 | container: { 13 | center: true, 14 | padding: "2rem", 15 | screens: { 16 | "2xl": "1400px", 17 | }, 18 | }, 19 | extend: { 20 | fontFamily: { 21 | sans: ['var(--font-outfit)', 'sans-serif'], 22 | outfit: ['var(--font-outfit)', 'sans-serif'], 23 | }, 24 | colors: { 25 | border: "hsl(var(--border))", 26 | input: "hsl(var(--input))", 27 | ring: "hsl(var(--ring))", 28 | background: "hsl(var(--background))", 29 | foreground: "hsl(var(--foreground))", 30 | primary: { 31 | DEFAULT: "hsl(var(--primary))", 32 | foreground: "hsl(var(--primary-foreground))", 33 | }, 34 | secondary: { 35 | DEFAULT: "hsl(var(--secondary))", 36 | foreground: "hsl(var(--secondary-foreground))", 37 | }, 38 | destructive: { 39 | DEFAULT: "hsl(var(--destructive))", 40 | foreground: "hsl(var(--destructive-foreground))", 41 | }, 42 | muted: { 43 | DEFAULT: "hsl(var(--muted))", 44 | foreground: "hsl(var(--muted-foreground))", 45 | }, 46 | accent: { 47 | DEFAULT: "hsl(var(--accent))", 48 | foreground: "hsl(var(--accent-foreground))", 49 | }, 50 | popover: { 51 | DEFAULT: "hsl(var(--popover))", 52 | foreground: "hsl(var(--popover-foreground))", 53 | }, 54 | card: { 55 | DEFAULT: "hsl(var(--card))", 56 | foreground: "hsl(var(--card-foreground))", 57 | }, 58 | // YUV.AI brand colors 59 | yuv: { 60 | purple: "#8844ee", 61 | yellow: "#ffdd22", 62 | white: "#ffffff", 63 | } 64 | }, 65 | borderRadius: { 66 | lg: "var(--radius)", 67 | md: "calc(var(--radius) - 2px)", 68 | sm: "calc(var(--radius) - 4px)", 69 | }, 70 | keyframes: { 71 | "accordion-down": { 72 | from: { height: 0 }, 73 | to: { height: "var(--radix-accordion-content-height)" }, 74 | }, 75 | "accordion-up": { 76 | from: { height: "var(--radix-accordion-content-height)" }, 77 | to: { height: 0 }, 78 | }, 79 | }, 80 | animation: { 81 | "accordion-down": "accordion-down 0.2s ease-out", 82 | "accordion-up": "accordion-up 0.2s ease-out", 83 | }, 84 | }, 85 | }, 86 | plugins: [require("tailwindcss-animate")], 87 | } -------------------------------------------------------------------------------- /PLAN.md: -------------------------------------------------------------------------------- 1 | # NCM Boilerplate Development Plan 2 | 3 | ## Overview 4 | This document outlines the development tasks and progress for the NCM Boilerplate project by Yuval Avidani. 5 | 6 | ## Tasks 7 | 8 | ### 1. Project Setup and Configuration ✅ 9 | - [x] Initialize Next.js project with TypeScript, Tailwind, and App Router - *Completed on [Current Date]* 10 | - [x] Setup ESLint and other development tools - *Completed on [Current Date]* 11 | - [x] Configure Git repository - *Completed on [Current Date]* 12 | 13 | ### 2. Authentication ✅ 14 | - [x] Integrate Clerk for authentication - *Completed on [Current Date]* 15 | - [x] Configure middleware for protected routes - *Completed on [Current Date]* 16 | - [x] Create sign-in and sign-up pages - *Completed on [Current Date]* 17 | 18 | ### 3. Database Integration ✅ 19 | - [x] Setup MongoDB connection with Mongoose - *Completed on [Current Date]* 20 | - [x] Create database connection utility - *Completed on [Current Date]* 21 | - [x] Add test endpoint for database verification - *Completed on [Current Date]* 22 | 23 | ### 4. UI Framework ⏳ (In Progress) 24 | - [ ] Setup ShadCN UI components - *Started on [Current Date]* 25 | - [ ] Create dark/light mode toggle - *Pending* 26 | - [ ] Design responsive layout components - *Pending* 27 | - [ ] Build navigation components - *Pending* 28 | 29 | ### 5. Dashboard Development ⏳ (In Progress) 30 | - [ ] Create dashboard layout - *Started on [Current Date]* 31 | - [ ] Design dashboard home page - *Pending* 32 | - [ ] Add user profile section - *Pending* 33 | - [ ] Implement settings page - *Pending* 34 | 35 | ### 6. Feature Components 🔄 (Not Started) 36 | - [ ] Create beautiful charts and data visualization components - *Pending* 37 | - [ ] Build comprehensive forms with validation - *Pending* 38 | - [ ] Implement data tables with sorting and filtering - *Pending* 39 | - [ ] Add notification system - *Pending* 40 | 41 | ### 7. Animations and Effects 🔄 (Not Started) 42 | - [ ] Add page transitions - *Pending* 43 | - [ ] Implement loading states and skeletons - *Pending* 44 | - [ ] Create micro-interactions and hover effects - *Pending* 45 | - [ ] Add encryption/decryption visual effects - *Pending* 46 | 47 | ### 8. Documentation and Finalization 🔄 (Not Started) 48 | - [ ] Complete inline code documentation - *Pending* 49 | - [ ] Update README with final information - *Pending* 50 | - [ ] Create example usage documentation - *Pending* 51 | - [ ] Final testing and bug fixes - *Pending* 52 | 53 | ## Progress Log 54 | 55 | ### [Current Date] 56 | - Initialized project with Next.js 14, TypeScript, and Tailwind CSS 57 | - Set up Clerk authentication with protected routes 58 | - Configured MongoDB connection with Mongoose 59 | - Created development plan and README 60 | - Started work on UI components and dashboard layout 61 | 62 | ## Next Steps 63 | - Complete the ShadCN UI integration 64 | - Implement dark/light mode toggle 65 | - Build responsive dashboard layout 66 | - Create beautiful data visualization components 67 | - Add animations and visual effects -------------------------------------------------------------------------------- /src/components/sidebar.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import Link from "next/link" 4 | import { usePathname } from "next/navigation" 5 | import { 6 | ChevronLeft, 7 | LayoutDashboard, 8 | Settings, 9 | Layers, 10 | BarChart3, 11 | Users, 12 | FileText, 13 | Bell, 14 | Home 15 | } from "lucide-react" 16 | import { cn } from "@/lib/utils" 17 | import { Button } from "@/components/ui/button" 18 | import { useState } from "react" 19 | 20 | interface SidebarProps { 21 | className?: string 22 | } 23 | 24 | export function Sidebar({ className }: SidebarProps) { 25 | const pathname = usePathname() 26 | const [collapsed, setCollapsed] = useState(false) 27 | 28 | return ( 29 |
36 |
37 | {/* Empty space for consistency with header height */} 38 |
39 |
40 | 91 |
92 | 93 | {/* Collapse button */} 94 | 105 |
106 | ) 107 | } 108 | 109 | interface NavItemProps { 110 | href: string 111 | icon: React.ComponentType<{ className?: string }> 112 | text: string 113 | active?: boolean 114 | collapsed?: boolean 115 | } 116 | 117 | function NavItem({ 118 | href, 119 | icon: Icon, 120 | text, 121 | active, 122 | collapsed, 123 | }: NavItemProps) { 124 | return ( 125 | 132 | 133 | {!collapsed && {text}} 134 | 135 | ) 136 | } -------------------------------------------------------------------------------- /src/app/sign-in/[[...sign-in]]/page.tsx: -------------------------------------------------------------------------------- 1 | import { SignIn } from '@clerk/nextjs'; 2 | import Link from 'next/link'; 3 | import { ArrowRight } from 'lucide-react'; 4 | import { Header } from '@/components/header'; 5 | 6 | export default function SignInPage() { 7 | return ( 8 |
9 | {/* Enhanced background elements */} 10 |
11 | 12 | {/* Enhanced decorative elements */} 13 |
14 |
15 |
16 |
17 | 18 |
19 | 20 |
21 |
22 |
23 | Y 24 | 25 | YUV.AI 26 | 27 |
28 |

Welcome back

29 |

Sign in to your account to continue

30 |
31 | 32 |
33 |
34 | 53 |
54 |
55 | 56 |
57 |

58 | Don't have an account?{' '} 59 | 60 | Sign up 61 | 62 |

63 |
64 | 65 | {/* Enhanced animated decorative elements */} 66 |
67 | 68 |
69 |
70 |
71 |
72 | ); 73 | } 74 | -------------------------------------------------------------------------------- /src/app/sign-up/[[...sign-up]]/page.tsx: -------------------------------------------------------------------------------- 1 | import { SignUp } from '@clerk/nextjs'; 2 | import Link from 'next/link'; 3 | import { ArrowRight } from 'lucide-react'; 4 | import { Header } from '@/components/header'; 5 | 6 | export default function SignUpPage() { 7 | return ( 8 |
9 | {/* Enhanced background elements */} 10 |
11 | 12 | {/* Enhanced decorative elements */} 13 |
14 |
15 |
16 |
17 | 18 |
19 | 20 |
21 |
22 |
23 | Y 24 | 25 | YUV.AI 26 | 27 |
28 |

Create your account

29 |

Join us and start building amazing projects

30 |
31 | 32 |
33 |
34 | 53 |
54 |
55 | 56 |
57 |

58 | Already have an account?{' '} 59 | 60 | Sign in 61 | 62 |

63 |
64 | 65 | {/* Enhanced animated decorative elements */} 66 |
67 | 68 |
69 |
70 |
71 |
72 | ); 73 | } 74 | -------------------------------------------------------------------------------- /src/app/globals.css: -------------------------------------------------------------------------------- 1 | @import "tailwindcss"; 2 | @import "tw-animate-css"; 3 | 4 | @custom-variant dark (&:is(.dark *)); 5 | 6 | @theme inline { 7 | --color-background: var(--background); 8 | --color-foreground: var(--foreground); 9 | --font-sans: var(--font-outfit); 10 | --font-mono: var(--font-outfit); 11 | --color-sidebar-ring: var(--sidebar-ring); 12 | --color-sidebar-border: var(--sidebar-border); 13 | --color-sidebar-accent-foreground: var(--sidebar-accent-foreground); 14 | --color-sidebar-accent: var(--sidebar-accent); 15 | --color-sidebar-primary-foreground: var(--sidebar-primary-foreground); 16 | --color-sidebar-primary: var(--sidebar-primary); 17 | --color-sidebar-foreground: var(--sidebar-foreground); 18 | --color-sidebar: var(--sidebar); 19 | --color-chart-5: var(--chart-5); 20 | --color-chart-4: var(--chart-4); 21 | --color-chart-3: var(--chart-3); 22 | --color-chart-2: var(--chart-2); 23 | --color-chart-1: var(--chart-1); 24 | --color-ring: var(--ring); 25 | --color-input: var(--input); 26 | --color-border: var(--border); 27 | --color-destructive: var(--destructive); 28 | --color-accent-foreground: var(--accent-foreground); 29 | --color-accent: var(--accent); 30 | --color-muted-foreground: var(--muted-foreground); 31 | --color-muted: var(--muted); 32 | --color-secondary-foreground: var(--secondary-foreground); 33 | --color-secondary: var(--secondary); 34 | --color-primary-foreground: var(--primary-foreground); 35 | --color-primary: var(--primary); 36 | --color-popover-foreground: var(--popover-foreground); 37 | --color-popover: var(--popover); 38 | --color-card-foreground: var(--card-foreground); 39 | --color-card: var(--card); 40 | --radius-sm: calc(var(--radius) - 4px); 41 | --radius-md: calc(var(--radius) - 2px); 42 | --radius-lg: var(--radius); 43 | --radius-xl: calc(var(--radius) + 4px); 44 | } 45 | 46 | :root { 47 | --radius: 0.625rem; 48 | --background: oklch(1 0 0); 49 | --foreground: oklch(0.145 0 0); 50 | --card: oklch(1 0 0); 51 | --card-foreground: oklch(0.145 0 0); 52 | --popover: oklch(1 0 0); 53 | --popover-foreground: oklch(0.145 0 0); 54 | --primary: oklch(0.205 0 0); 55 | --primary-foreground: oklch(0.985 0 0); 56 | --secondary: oklch(0.97 0 0); 57 | --secondary-foreground: oklch(0.205 0 0); 58 | --muted: oklch(0.97 0 0); 59 | --muted-foreground: oklch(0.556 0 0); 60 | --accent: oklch(0.97 0 0); 61 | --accent-foreground: oklch(0.205 0 0); 62 | --destructive: oklch(0.577 0.245 27.325); 63 | --border: oklch(0.922 0 0); 64 | --input: oklch(0.922 0 0); 65 | --ring: oklch(0.708 0 0); 66 | --chart-1: oklch(0.646 0.222 41.116); 67 | --chart-2: oklch(0.6 0.118 184.704); 68 | --chart-3: oklch(0.398 0.07 227.392); 69 | --chart-4: oklch(0.828 0.189 84.429); 70 | --chart-5: oklch(0.769 0.188 70.08); 71 | --sidebar: oklch(0.985 0 0); 72 | --sidebar-foreground: oklch(0.145 0 0); 73 | --sidebar-primary: oklch(0.205 0 0); 74 | --sidebar-primary-foreground: oklch(0.985 0 0); 75 | --sidebar-accent: oklch(0.97 0 0); 76 | --sidebar-accent-foreground: oklch(0.205 0 0); 77 | --sidebar-border: oklch(0.922 0 0); 78 | --sidebar-ring: oklch(0.708 0 0); 79 | } 80 | 81 | .dark { 82 | --background: oklch(0.145 0 0); 83 | --foreground: oklch(0.985 0 0); 84 | --card: oklch(0.205 0 0); 85 | --card-foreground: oklch(0.985 0 0); 86 | --popover: oklch(0.205 0 0); 87 | --popover-foreground: oklch(0.985 0 0); 88 | --primary: oklch(0.922 0 0); 89 | --primary-foreground: oklch(0.205 0 0); 90 | --secondary: oklch(0.269 0 0); 91 | --secondary-foreground: oklch(0.985 0 0); 92 | --muted: oklch(0.269 0 0); 93 | --muted-foreground: oklch(0.708 0 0); 94 | --accent: oklch(0.269 0 0); 95 | --accent-foreground: oklch(0.985 0 0); 96 | --destructive: oklch(0.704 0.191 22.216); 97 | --border: oklch(1 0 0 / 10%); 98 | --input: oklch(1 0 0 / 15%); 99 | --ring: oklch(0.556 0 0); 100 | --chart-1: oklch(0.488 0.243 264.376); 101 | --chart-2: oklch(0.696 0.17 162.48); 102 | --chart-3: oklch(0.769 0.188 70.08); 103 | --chart-4: oklch(0.627 0.265 303.9); 104 | --chart-5: oklch(0.645 0.246 16.439); 105 | --sidebar: oklch(0.205 0 0); 106 | --sidebar-foreground: oklch(0.985 0 0); 107 | --sidebar-primary: oklch(0.488 0.243 264.376); 108 | --sidebar-primary-foreground: oklch(0.985 0 0); 109 | --sidebar-accent: oklch(0.269 0 0); 110 | --sidebar-accent-foreground: oklch(0.985 0 0); 111 | --sidebar-border: oklch(1 0 0 / 10%); 112 | --sidebar-ring: oklch(0.556 0 0); 113 | } 114 | 115 | @layer base { 116 | * { 117 | @apply border-border outline-ring/50; 118 | } 119 | body { 120 | @apply bg-background text-foreground; 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/components/ui/sheet.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import * as React from "react" 4 | import * as SheetPrimitive from "@radix-ui/react-dialog" 5 | import { XIcon } from "lucide-react" 6 | 7 | import { cn } from "@/lib/utils" 8 | 9 | function Sheet({ ...props }: React.ComponentProps) { 10 | return 11 | } 12 | 13 | function SheetTrigger({ 14 | ...props 15 | }: React.ComponentProps) { 16 | return 17 | } 18 | 19 | function SheetClose({ 20 | ...props 21 | }: React.ComponentProps) { 22 | return 23 | } 24 | 25 | function SheetPortal({ 26 | ...props 27 | }: React.ComponentProps) { 28 | return 29 | } 30 | 31 | function SheetOverlay({ 32 | className, 33 | ...props 34 | }: React.ComponentProps) { 35 | return ( 36 | 44 | ) 45 | } 46 | 47 | function SheetContent({ 48 | className, 49 | children, 50 | side = "right", 51 | ...props 52 | }: React.ComponentProps & { 53 | side?: "top" | "right" | "bottom" | "left" 54 | }) { 55 | return ( 56 | 57 | 58 | 74 | {children} 75 | 76 | 77 | Close 78 | 79 | 80 | 81 | ) 82 | } 83 | 84 | function SheetHeader({ className, ...props }: React.ComponentProps<"div">) { 85 | return ( 86 |
91 | ) 92 | } 93 | 94 | function SheetFooter({ className, ...props }: React.ComponentProps<"div">) { 95 | return ( 96 |
101 | ) 102 | } 103 | 104 | function SheetTitle({ 105 | className, 106 | ...props 107 | }: React.ComponentProps) { 108 | return ( 109 | 114 | ) 115 | } 116 | 117 | function SheetDescription({ 118 | className, 119 | ...props 120 | }: React.ComponentProps) { 121 | return ( 122 | 127 | ) 128 | } 129 | 130 | export { 131 | Sheet, 132 | SheetTrigger, 133 | SheetClose, 134 | SheetContent, 135 | SheetHeader, 136 | SheetFooter, 137 | SheetTitle, 138 | SheetDescription, 139 | } 140 | -------------------------------------------------------------------------------- /src/components/header.tsx: -------------------------------------------------------------------------------- 1 | "use client" 2 | 3 | import { useState, useEffect } from "react" 4 | import Link from "next/link" 5 | import { UserButton, SignedIn, SignedOut } from "@clerk/nextjs" 6 | import { Menu } from "lucide-react" 7 | 8 | import { ThemeToggle } from "@/components/theme-toggle" 9 | import { Button } from "@/components/ui/button" 10 | import { Sheet, SheetContent, SheetTrigger } from "@/components/ui/sheet" 11 | 12 | export function Header() { 13 | const [mounted, setMounted] = useState(false) 14 | 15 | // Set mounted to true after component mounts 16 | useEffect(() => { 17 | setMounted(true) 18 | }, []) 19 | 20 | // Ensure a consistent initial render that matches the server 21 | // A simple version without any interactive elements while not mounted 22 | if (!mounted) { 23 | return ( 24 |
25 |
26 |
27 |
28 | 29 | Y 30 | 31 | YUV.AI 32 | 33 | 34 |
35 |
36 |
37 |
38 |
39 |
40 | ) 41 | } 42 | 43 | return ( 44 |
45 |
46 |
47 | 48 | 49 | 53 | 54 | 55 |
56 | 57 |
58 |
59 |
60 | 61 | Y 62 | 63 | YUV.AI 64 | 65 | 66 |
67 | 68 | 69 | 83 | 84 | 85 |
86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 |
99 |
100 |
101 | ) 102 | } 103 | 104 | function MobileNav() { 105 | return ( 106 |
107 | 108 | Home 109 | 110 | 111 | 112 | Dashboard 113 | 114 | 115 | Projects 116 | 117 | 118 | Analytics 119 | 120 | 121 | Settings 122 | 123 | 124 | 125 | 126 | Sign In 127 | 128 | 129 | Sign Up 130 | 131 | 132 |
133 | ) 134 | } -------------------------------------------------------------------------------- /src/app/projects/page.tsx: -------------------------------------------------------------------------------- 1 | import { Folder, Clock, Users, Check, Filter, Plus } from "lucide-react"; 2 | import { auth, currentUser } from "@clerk/nextjs/server"; 3 | import { Button } from "@/components/ui/button"; 4 | import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; 5 | import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar"; 6 | 7 | export default async function ProjectsPage() { 8 | const session = await auth(); 9 | const user = await currentUser(); 10 | const userName = user?.firstName || user?.username || "User"; 11 | 12 | const projects = [ 13 | { 14 | id: 1, 15 | name: "AI Content Generator", 16 | description: "A tool to generate various types of content using AI", 17 | status: "In Progress", 18 | progress: 65, 19 | members: 4, 20 | updated: "2 hours ago", 21 | image: "https://picsum.photos/id/1/200" 22 | }, 23 | { 24 | id: 2, 25 | name: "Data Visualization Dashboard", 26 | description: "Interactive dashboard for visualizing complex data sets", 27 | status: "Completed", 28 | progress: 100, 29 | members: 3, 30 | updated: "1 day ago", 31 | image: "https://picsum.photos/id/2/200" 32 | }, 33 | { 34 | id: 3, 35 | name: "E-commerce Platform", 36 | description: "Modern e-commerce solution with advanced features", 37 | status: "In Progress", 38 | progress: 40, 39 | members: 5, 40 | updated: "Just now", 41 | image: "https://picsum.photos/id/3/200" 42 | }, 43 | { 44 | id: 4, 45 | name: "Mobile App Redesign", 46 | description: "Redesigning the UI/UX of our flagship mobile application", 47 | status: "Planning", 48 | progress: 10, 49 | members: 2, 50 | updated: "3 days ago", 51 | image: "https://picsum.photos/id/4/200" 52 | }, 53 | { 54 | id: 5, 55 | name: "Customer Analytics Tool", 56 | description: "Tool for analyzing customer behavior and interactions", 57 | status: "In Progress", 58 | progress: 75, 59 | members: 3, 60 | updated: "5 hours ago", 61 | image: "https://picsum.photos/id/5/200" 62 | }, 63 | { 64 | id: 6, 65 | name: "API Integration Service", 66 | description: "Service for seamless integration with third-party APIs", 67 | status: "Completed", 68 | progress: 100, 69 | members: 2, 70 | updated: "1 week ago", 71 | image: "https://picsum.photos/id/6/200" 72 | } 73 | ]; 74 | 75 | return ( 76 |
77 |
78 |
79 |

Projects

80 |

81 | Manage and monitor your project portfolio 82 |

83 |
84 |
85 | 89 | 93 |
94 |
95 | 96 |
97 | {projects.map((project) => ( 98 | 99 | 100 |
101 | {project.name} 102 |
103 | {project.name} 104 | {project.description} 105 |
106 | 107 |
108 |
109 | 114 | {project.status} 115 |
116 |
117 | 118 | {project.updated} 119 |
120 |
121 | 122 |
123 |
131 |
132 | 133 |
134 |
135 | {Array.from({ length: project.members }).map((_, i) => ( 136 | 137 | 138 | U{i+1} 139 | 140 | ))} 141 |
142 | 143 |
144 |
145 |
146 | ))} 147 |
148 |
149 | ); 150 | } -------------------------------------------------------------------------------- /src/components/charts/bar-chart.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState, useEffect } from "react"; 4 | import { 5 | Bar, 6 | BarChart, 7 | CartesianGrid, 8 | Legend, 9 | ResponsiveContainer, 10 | Tooltip, 11 | XAxis, 12 | YAxis, 13 | } from "recharts"; 14 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; 15 | import { useTheme } from "next-themes"; 16 | 17 | interface BarChartProps { 18 | title?: string; 19 | description?: string; 20 | data: { 21 | name: string; 22 | [key: string]: number | string; 23 | }[]; 24 | categories: { 25 | name: string; 26 | key: string; 27 | color: string; 28 | darkColor?: string; 29 | }[]; 30 | className?: string; 31 | } 32 | 33 | export function BarChartComponent({ 34 | title, 35 | description, 36 | data, 37 | categories, 38 | className, 39 | }: BarChartProps) { 40 | const { theme, resolvedTheme } = useTheme(); 41 | const [mounted, setMounted] = useState(false); 42 | const [activeIndex, setActiveIndex] = useState(null); 43 | 44 | // Set mounted to true after the component mounts 45 | useEffect(() => { 46 | setMounted(true); 47 | }, []); 48 | 49 | const isDark = mounted ? resolvedTheme === "dark" : false; 50 | 51 | const handleMouseEnter = (_: any, index: number) => { 52 | setActiveIndex(index); 53 | }; 54 | 55 | const handleMouseLeave = () => { 56 | setActiveIndex(null); 57 | }; 58 | 59 | const getPercentChange = (key: string) => { 60 | if (data.length < 2) return 0; 61 | 62 | const current = Number(data[data.length - 1][key] || 0); 63 | const previous = Number(data[data.length - 2][key] || 0); 64 | 65 | if (previous === 0) return 0; 66 | return ((current - previous) / previous) * 100; 67 | }; 68 | 69 | const totalPercentChange = categories.reduce((sum, category) => { 70 | return sum + getPercentChange(category.key); 71 | }, 0) / categories.length; 72 | 73 | const trend = totalPercentChange >= 0 ? "up" : "down"; 74 | 75 | // If not mounted yet, render a minimal version that won't cause hydration issues 76 | if (!mounted) { 77 | return ( 78 | 79 | 80 | {title || "Bar Chart"} 81 | 82 | {description || "Comparison of data across categories"} 83 | 84 | 85 | 86 |
87 |

88 | Loading chart data... 89 |

90 |
91 |
92 | {/* Empty space for chart */} 93 |
94 |
95 |
96 | ); 97 | } 98 | 99 | return ( 100 | 101 | 102 | {title || "Bar Chart"} 103 | 104 | {description || "Comparison of data across categories"} 105 | 106 | 107 | 108 |
109 |

110 | Trending{" "} 111 | 112 | {trend} by {Math.abs(totalPercentChange).toFixed(1)}% 113 | {" "} 114 | this month 115 |

116 |
117 |
118 | 119 | 131 | 136 | 143 | `${value}`} 149 | /> 150 | 166 | { 172 | return {value}; 173 | }} 174 | /> 175 | {categories.map((category) => ( 176 | handleMouseEnter(data, index)} 185 | activeBar={{ 186 | stroke: isDark ? "#fff" : "#000", 187 | strokeWidth: 1, 188 | strokeOpacity: 0.1, 189 | }} 190 | /> 191 | ))} 192 | 193 | 194 |
195 |
196 |
197 | ); 198 | } -------------------------------------------------------------------------------- /src/components/charts/pie-chart.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState, useEffect } from "react"; 4 | import { 5 | Cell, 6 | Pie, 7 | PieChart, 8 | ResponsiveContainer, 9 | Sector, 10 | Tooltip, 11 | } from "recharts"; 12 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; 13 | import { useTheme } from "next-themes"; 14 | 15 | interface PieChartProps { 16 | title?: string; 17 | description?: string; 18 | data: { 19 | name: string; 20 | value: number; 21 | }[]; 22 | className?: string; 23 | donut?: boolean; 24 | totalText?: string; 25 | } 26 | 27 | export function PieChartComponent({ 28 | title, 29 | description, 30 | data, 31 | className, 32 | donut = true, 33 | totalText, 34 | }: PieChartProps) { 35 | const { theme, resolvedTheme } = useTheme(); 36 | const [mounted, setMounted] = useState(false); 37 | const [activeIndex, setActiveIndex] = useState(null); 38 | 39 | // Set mounted to true after the component mounts 40 | useEffect(() => { 41 | setMounted(true); 42 | }, []); 43 | 44 | const isDark = mounted ? resolvedTheme === "dark" : false; 45 | 46 | const handleMouseEnter = (_: any, index: number) => { 47 | setActiveIndex(index); 48 | }; 49 | 50 | const handleMouseLeave = () => { 51 | setActiveIndex(null); 52 | }; 53 | 54 | // Stable colors that work for both light and dark themes 55 | const COLORS = [ 56 | '#c084fc', // Purple 57 | '#fef08a', // Yellow 58 | '#93c5fd', // Blue 59 | '#86efac', // Green 60 | '#fb7185', // Red 61 | '#67e8f9', // Cyan 62 | '#fdba74', // Orange 63 | '#d8b4fe', // Violet 64 | ]; 65 | 66 | const renderActiveShape = (props: any) => { 67 | const { 68 | cx, 69 | cy, 70 | innerRadius, 71 | outerRadius, 72 | startAngle, 73 | endAngle, 74 | fill, 75 | percent, 76 | name, 77 | value, 78 | } = props; 79 | 80 | return ( 81 | 82 | 91 | {donut && totalText && mounted && ( 92 | 100 | {totalText} 101 | 102 | )} 103 | 104 | ); 105 | }; 106 | 107 | const total = data.reduce((sum, item) => sum + item.value, 0); 108 | 109 | // If not mounted yet, render a minimal version that won't cause hydration issues 110 | if (!mounted) { 111 | return ( 112 | 113 | 114 | {title || "Pie Chart"} 115 | 116 | {description || "Distribution of values"} 117 | 118 | 119 | 120 |
121 | {/* Empty space for chart */} 122 |
123 |
124 | {data.map((entry, index) => ( 125 |
126 |
130 | {entry.name} 131 |
132 | ))} 133 |
134 | 135 | 136 | ); 137 | } 138 | 139 | return ( 140 | 141 | 142 | {title || "Pie Chart"} 143 | 144 | {description || "Distribution of values"} 145 | 146 | 147 | 148 |
149 | 150 | 151 | 168 | {data.map((entry, index) => ( 169 | 175 | ))} 176 | 177 | { 188 | const percentage = ((value / total) * 100).toFixed(1); 189 | return [`${value} (${percentage}%)`, "Value"]; 190 | }} 191 | /> 192 | 193 | 194 |
195 | 196 |
197 | {data.map((entry, index) => ( 198 |
199 |
203 | {entry.name} 204 |
205 | ))} 206 |
207 | 208 | 209 | ); 210 | } -------------------------------------------------------------------------------- /src/components/charts/line-chart.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState, useEffect } from "react"; 4 | import { 5 | CartesianGrid, 6 | Legend, 7 | Line, 8 | LineChart, 9 | ResponsiveContainer, 10 | Tooltip, 11 | XAxis, 12 | YAxis, 13 | } from "recharts"; 14 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; 15 | import { useTheme } from "next-themes"; 16 | 17 | interface LineChartProps { 18 | title?: string; 19 | description?: string; 20 | data: { 21 | name: string; 22 | [key: string]: number | string; 23 | }[]; 24 | series: { 25 | name: string; 26 | key: string; 27 | color: string; 28 | darkColor?: string; 29 | }[]; 30 | className?: string; 31 | } 32 | 33 | export function LineChartComponent({ 34 | title, 35 | description, 36 | data, 37 | series, 38 | className, 39 | }: LineChartProps) { 40 | const { theme, resolvedTheme } = useTheme(); 41 | const [mounted, setMounted] = useState(false); 42 | const [activeLineIndex, setActiveLineIndex] = useState(null); 43 | 44 | // Set mounted to true after the component mounts 45 | useEffect(() => { 46 | setMounted(true); 47 | }, []); 48 | 49 | const isDark = mounted ? resolvedTheme === "dark" : false; 50 | 51 | const handleMouseEnter = (_: any, index: number) => { 52 | setActiveLineIndex(index); 53 | }; 54 | 55 | const handleMouseLeave = () => { 56 | setActiveLineIndex(null); 57 | }; 58 | 59 | const getPercentChange = (key: string) => { 60 | if (data.length < 2) return 0; 61 | 62 | const current = Number(data[data.length - 1][key] || 0); 63 | const previous = Number(data[data.length - 2][key] || 0); 64 | 65 | if (previous === 0) return 0; 66 | return ((current - previous) / previous) * 100; 67 | }; 68 | 69 | // Calculate average trend for all series 70 | const totalPercentChange = series.reduce((sum, serie) => { 71 | return sum + getPercentChange(serie.key); 72 | }, 0) / series.length; 73 | 74 | const trend = totalPercentChange >= 0 ? "up" : "down"; 75 | 76 | // If not mounted yet, render a minimal version that won't cause hydration issues 77 | if (!mounted) { 78 | return ( 79 | 80 | 81 | {title || "Line Chart"} 82 | 83 | {description || "Showing trends over time"} 84 | 85 | 86 | 87 |
88 |

89 | Loading chart data... 90 |

91 |
92 |
93 | {/* Empty space for chart */} 94 |
95 |
96 |
97 | ); 98 | } 99 | 100 | return ( 101 | 102 | 103 | {title || "Line Chart"} 104 | 105 | {description || "Showing trends over time"} 106 | 107 | 108 | 109 |
110 |

111 | Trending{" "} 112 | 113 | {trend} by {Math.abs(totalPercentChange).toFixed(1)}% 114 | {" "} 115 | this month 116 |

117 |
118 |
119 | 120 | 130 | 135 | 142 | `${value}`} 148 | /> 149 | [`${value}`, ""]} 165 | /> 166 | { 172 | return {value}; 173 | }} 174 | /> 175 | {series.map((serie, index) => ( 176 | handleMouseEnter(data, index)} 196 | animationDuration={1500} 197 | animationEasing="ease-out" 198 | /> 199 | ))} 200 | 201 | 202 |
203 |
204 |
205 | ); 206 | } -------------------------------------------------------------------------------- /src/components/charts/area-chart.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useState, useEffect } from "react"; 4 | import { 5 | Area, 6 | AreaChart, 7 | CartesianGrid, 8 | ResponsiveContainer, 9 | Tooltip, 10 | XAxis, 11 | YAxis, 12 | } from "recharts"; 13 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; 14 | import { useTheme } from "next-themes"; 15 | 16 | interface AreaChartProps { 17 | title?: string; 18 | description?: string; 19 | data: { 20 | name: string; 21 | value: number; 22 | previousValue?: number; 23 | }[]; 24 | className?: string; 25 | } 26 | 27 | export function AreaChartComponent({ 28 | title, 29 | description, 30 | data, 31 | className, 32 | }: AreaChartProps) { 33 | const { theme, resolvedTheme } = useTheme(); 34 | const [mounted, setMounted] = useState(false); 35 | const [activeIndex, setActiveIndex] = useState(null); 36 | 37 | // Set mounted to true after the component mounts 38 | useEffect(() => { 39 | setMounted(true); 40 | }, []); 41 | 42 | const isDark = mounted ? resolvedTheme === "dark" : false; 43 | 44 | const handleMouseEnter = (_: any, index: number) => { 45 | setActiveIndex(index); 46 | }; 47 | 48 | const handleMouseLeave = () => { 49 | setActiveIndex(null); 50 | }; 51 | 52 | const getPercentChange = () => { 53 | if (data.length < 2) return 0; 54 | 55 | const currentValue = data[data.length - 1].value; 56 | const previousValue = data[data.length - 2].value; 57 | 58 | if (previousValue === 0) return 0; 59 | return ((currentValue - previousValue) / previousValue) * 100; 60 | }; 61 | 62 | const percentChange = getPercentChange(); 63 | const trend = percentChange >= 0 ? "up" : "down"; 64 | 65 | // If not mounted yet, render a minimal version that won't cause hydration issues 66 | if (!mounted) { 67 | return ( 68 | 69 | 70 | {title || "Area Chart"} 71 | 72 | {description || "Showing data trends over time"} 73 | 74 | 75 | 76 |
77 |

78 | Loading chart data... 79 |

80 |
81 |
82 | {/* Empty space for chart */} 83 |
84 |
85 |
86 | ); 87 | } 88 | 89 | return ( 90 | 91 | 92 | {title || "Area Chart"} 93 | 94 | {description || "Showing data trends over time"} 95 | 96 | 97 | 98 |
99 |

100 | Trending{" "} 101 | 102 | {trend} by {Math.abs(percentChange).toFixed(1)}% 103 | {" "} 104 | this month 105 |

106 |
107 |
108 | 109 | 119 | 120 | 121 | 126 | 131 | 132 | 133 | 138 | 143 | 144 | 145 | 150 | 157 | `${value}`} 163 | /> 164 | [`${value}`, "Value"]} 180 | /> 181 | 197 | {data[0]?.previousValue !== undefined && ( 198 | 208 | )} 209 | 210 | 211 |
212 |
213 |
214 | ); 215 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # NCM Business Portfolio 2 | 3 | ![NCM Business Portfolio](https://i.imgur.com/uZshILa.png) 4 | ![NCM Business Portfolio - Dashboard](https://i.imgur.com/QKlIlvQ.png) 5 | ![NCM Business Portfolio - Dashboard Bright](https://i.imgur.com/F8RVRH9.png) 6 | 7 | A modern business portfolio application built with Next.js 14, Clerk Authentication, TailwindCSS, and shadcn/ui components. 8 | 9 | ## 🚀 Features 10 | 11 | - **Modern UI/UX**: Beautiful, responsive design with glassmorphism effects 12 | - **Authentication**: Secure user authentication powered by Clerk 13 | - **Dashboard Analytics**: Interactive charts and visualization 14 | - **Project Management**: Create and manage project portfolios 15 | - **Theme Support**: Dark/Light mode toggle 16 | - **Mobile-First Design**: Fully responsive interface for all devices 17 | 18 | ## 🧰 Tech Stack 19 | 20 | - **Framework**: [Next.js 14](https://nextjs.org/) (App Router) 21 | - **Authentication**: [Clerk](https://clerk.dev/) 22 | - **Styling**: [TailwindCSS](https://tailwindcss.com/) 23 | - **UI Components**: [shadcn/ui](https://ui.shadcn.com/) 24 | - **Charts**: Recharts-based custom components 25 | - **Icons**: [Lucide React](https://lucide.dev/) 26 | - **Fonts**: Google Outfit font 27 | - **Database**: [MongoDB Atlas](https://www.mongodb.com/atlas/database) 28 | 29 | ## 📦 Installation 30 | 31 | ### Prerequisites 32 | 33 | - Node.js 18.17 or later 34 | - npm or yarn 35 | - Git 36 | - MongoDB Atlas account 37 | - Clerk account 38 | 39 | ### Recommended Development Tools 40 | 41 | For the best development experience, we recommend using one of these AI-powered tools: 42 | 43 | - [Cursor](https://cursor.sh/) - AI-powered code editor 44 | - [GitHub Copilot](https://github.com/features/copilot) - AI pair programmer 45 | - [Windsurf](https://www.phind.com/blog/code-editor) - AI coding assistant 46 | 47 | These tools will help you understand and modify the codebase more efficiently. 48 | 49 | ### Step-by-Step Setup Instructions 50 | 51 | 1. **Clone the repository** 52 | 53 | ```bash 54 | git clone https://github.com/yourusername/ncm-business-portfolio.git 55 | cd ncm-business-portfolio 56 | ``` 57 | 58 | 2. **Install dependencies** 59 | 60 | ```bash 61 | npm install 62 | # or 63 | yarn install 64 | ``` 65 | 66 | 3. **Set up Clerk Authentication** 67 | 68 | a. Create a [Clerk account](https://clerk.dev/sign-up) if you don't have one 69 | 70 | b. Create a new application: 71 | - Go to the Clerk Dashboard and click "Add Application" 72 | - Enter a name for your application 73 | - Select "Next.js" as the framework 74 | 75 | c. Configure your application: 76 | - In the Clerk Dashboard, go to your application 77 | - Go to "API Keys" in the sidebar 78 | - You'll find your "Publishable Key" and "Secret Key" 79 | - Copy these keys for your environment variables 80 | 81 | d. Configure sign-in options (optional): 82 | - In the Clerk Dashboard, go to "Authentication" → "Social Connections" 83 | - Enable the social login providers you want to support (e.g., Google, GitHub) 84 | - Follow the instructions to set up each provider 85 | 86 | e. Set up redirect URLs: 87 | - In "Authentication" → "Redirects", set the following: 88 | - Sign-in: `/sign-in` 89 | - Sign-up: `/sign-up` 90 | - After sign-in: `/dashboard` 91 | - After sign-up: `/dashboard` 92 | 93 | 4. **Set up MongoDB Atlas** 94 | 95 | a. Create a [MongoDB Atlas account](https://www.mongodb.com/cloud/atlas/register) if you don't have one 96 | 97 | b. Create a new project and cluster (the free tier works perfectly) 98 | 99 | c. Set up database access: 100 | - Create a database user with password authentication 101 | - Remember to save these credentials securely 102 | 103 | d. Set up network access: 104 | - Add your current IP address to the IP Access List 105 | - For development, you can allow access from anywhere (0.0.0.0/0) 106 | 107 | e. Get your connection string: 108 | - Go to your cluster and click "Connect" 109 | - Select "Connect your application" 110 | - Copy the connection string (it will look like: `mongodb+srv://username:@cluster0.xxxxx.mongodb.net/?retryWrites=true&w=majority`) 111 | - Replace `` with your database user's password 112 | 113 | 5. **Set up environment variables** 114 | 115 | Create a `.env.local` file in the root directory with the following variables: 116 | 117 | ``` 118 | # Clerk Authentication 119 | NEXT_PUBLIC_CLERK_PUBLISHABLE_KEY=your_clerk_publishable_key 120 | CLERK_SECRET_KEY=your_clerk_secret_key 121 | NEXT_PUBLIC_CLERK_SIGN_IN_URL=/sign-in 122 | NEXT_PUBLIC_CLERK_SIGN_UP_URL=/sign-up 123 | NEXT_PUBLIC_CLERK_AFTER_SIGN_IN_URL=/dashboard 124 | NEXT_PUBLIC_CLERK_AFTER_SIGN_UP_URL=/dashboard 125 | 126 | # MongoDB Atlas 127 | MONGODB_URI=your_mongodb_connection_string 128 | MONGODB_DB_NAME=your_database_name 129 | ``` 130 | 131 | 6. **Run the development server** 132 | 133 | ```bash 134 | npm run dev 135 | # or 136 | yarn dev 137 | ``` 138 | 139 | 7. **Open your browser** 140 | 141 | Navigate to [http://localhost:3000](http://localhost:3000) to see the application. 142 | 143 | ## 📋 Key Pages 144 | 145 | - **Dashboard** (`/dashboard`): Main overview with statistics and project summaries 146 | - **Projects** (`/projects`): Project listing and management 147 | - **Analytics** (`/analytics`): Detailed analytics with interactive charts 148 | - **Settings** (`/settings`): User profile and application settings 149 | 150 | ## 🌟 Custom Components 151 | 152 | The application features several custom components: 153 | 154 | - **Charts**: Area, Bar, Line and Pie charts with responsive design 155 | - **Header**: Responsive navigation with mobile drawer 156 | - **Theme Toggle**: Light/Dark mode switcher 157 | - **Cards**: Beautiful glassmorphism-style cards for content display 158 | 159 | ## 📱 Responsive Design 160 | 161 | The application is built with a mobile-first approach and includes: 162 | 163 | - Responsive navigation (collapsible sidebar on mobile) 164 | - Fluid layouts that adapt to any screen size 165 | - Optimized content display for different devices 166 | 167 | ## 🧩 Project Structure 168 | 169 | ``` 170 | ncm-business-portfolio/ 171 | ├── public/ # Static assets 172 | ├── src/ 173 | │ ├── app/ # App router pages 174 | │ │ ├── dashboard/ # Dashboard page 175 | │ │ ├── projects/ # Projects page 176 | │ │ ├── analytics/ # Analytics page 177 | │ │ ├── settings/ # Settings page 178 | │ │ └── ... 179 | │ ├── components/ # Reusable components 180 | │ │ ├── charts/ # Chart components 181 | │ │ ├── ui/ # shadcn/ui components 182 | │ │ └── ... 183 | │ ├── lib/ # Utility functions and shared logic 184 | │ └── models/ # MongoDB schema models 185 | ├── next.config.ts # Next.js configuration 186 | ├── tailwind.config.js # TailwindCSS configuration 187 | └── ... 188 | ``` 189 | 190 | ## 🚀 Deployment 191 | 192 | This application can be easily deployed on: 193 | 194 | - [Vercel](https://vercel.com/) 195 | - [Netlify](https://www.netlify.com/) 196 | - [Railway](https://railway.app/) 197 | 198 | ## 🔒 Authentication Flow 199 | 200 | The authentication is handled by Clerk and includes: 201 | 202 | - Sign up/Sign in pages 203 | - Protected routes 204 | - User profile management 205 | - Authentication middleware 206 | 207 | ### Understanding Clerk Authentication 208 | 209 | Once set up, Clerk provides: 210 | 211 | 1. **Pre-built components**: ``, ``, ``, etc. 212 | 2. **Auth hooks**: `useAuth()`, `useUser()`, etc. for accessing user data 213 | 3. **Middleware**: Protects routes based on authentication status 214 | 4. **Server-side helpers**: For accessing user data in server components 215 | 216 | Example of protecting a route: 217 | ```jsx 218 | // In your route component 219 | import { auth } from "@clerk/nextjs"; 220 | 221 | export default function ProtectedPage() { 222 | const { userId } = auth(); 223 | 224 | if (!userId) { 225 | // Handle unauthenticated state 226 | redirect("/sign-in"); 227 | } 228 | 229 | // Render content for authenticated users 230 | } 231 | ``` 232 | 233 | ## 🧪 Extending the Project 234 | 235 | To add new features to the project: 236 | 237 | 1. For new pages, create folders in the `src/app` directory 238 | 2. For new components, add them to the `src/components` directory 239 | 3. For database integrations, set up MongoDB models in the `src/models` directory 240 | 241 | ## 🌈 Customization 242 | 243 | Customize the look and feel of the application: 244 | 245 | - Edit `tailwind.config.js` to change theme colors 246 | - Modify `src/app/layout.tsx` to update global layout 247 | - Update fonts and styles in the theme configuration 248 | 249 | ## 👤 About the Developer 250 | 251 | Created with 💜 by [Yuval Avidani](https://linktr.ee/yuvai), AI Builder & Speaker 252 | 253 | - X: [@yuvalav](https://x.com/yuvalav) 254 | - Instagram: [@yuval_770](https://instagram.com/yuval_770) 255 | - Blog: [https://yuv.ai](https://yuv.ai) 256 | 257 | > "Fly High With YUV.AI" 258 | 259 | ## 📄 License 260 | 261 | This project is open source and available under the [MIT License](LICENSE). 262 | -------------------------------------------------------------------------------- /src/app/analytics/page.tsx: -------------------------------------------------------------------------------- 1 | import { auth, currentUser } from "@clerk/nextjs/server"; 2 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; 3 | import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; 4 | import { AreaChartComponent } from "@/components/charts/area-chart"; 5 | import { BarChartComponent } from "@/components/charts/bar-chart"; 6 | import { LineChartComponent } from "@/components/charts/line-chart"; 7 | import { PieChartComponent } from "@/components/charts/pie-chart"; 8 | import { ArrowUpRight, BadgePlus, Download, BarChart2, BarChart, Calendar, ArrowDown, ArrowUp } from "lucide-react"; 9 | import { Button } from "@/components/ui/button"; 10 | 11 | // Sample data for charts 12 | const visitorData = [ 13 | { name: "Jan", value: 2500, previousValue: 2200 }, 14 | { name: "Feb", value: 3000, previousValue: 2800 }, 15 | { name: "Mar", value: 2800, previousValue: 2600 }, 16 | { name: "Apr", value: 3200, previousValue: 2900 }, 17 | { name: "May", value: 3800, previousValue: 3200 }, 18 | { name: "Jun", value: 4200, previousValue: 3500 }, 19 | ]; 20 | 21 | const revenueData = [ 22 | { name: "Jan", value: 12500, previousValue: 10200 }, 23 | { name: "Feb", value: 14000, previousValue: 12800 }, 24 | { name: "Mar", value: 15800, previousValue: 14600 }, 25 | { name: "Apr", value: 18200, previousValue: 16900 }, 26 | { name: "May", value: 21800, previousValue: 19200 }, 27 | { name: "Jun", value: 25200, previousValue: 22500 }, 28 | ]; 29 | 30 | const projectData = [ 31 | { name: "Web App", value: 35 }, 32 | { name: "Mobile App", value: 25 }, 33 | { name: "Landing Page", value: 20 }, 34 | { name: "Dashboard", value: 15 }, 35 | { name: "Others", value: 5 }, 36 | ]; 37 | 38 | const deviceData = [ 39 | { name: "Jan", desktop: 1500, mobile: 900, tablet: 500 }, 40 | { name: "Feb", desktop: 2000, mobile: 1200, tablet: 600 }, 41 | { name: "Mar", desktop: 1800, mobile: 1400, tablet: 550 }, 42 | { name: "Apr", desktop: 2400, mobile: 1800, tablet: 700 }, 43 | { name: "May", desktop: 2800, mobile: 2100, tablet: 900 }, 44 | { name: "Jun", desktop: 3200, mobile: 2400, tablet: 1100 }, 45 | ]; 46 | 47 | const deviceCategories = [ 48 | { name: "Desktop", key: "desktop", color: "#d8b4fe", darkColor: "#c084fc" }, 49 | { name: "Mobile", key: "mobile", color: "#fef9c3", darkColor: "#fef08a" }, 50 | { name: "Tablet", key: "tablet", color: "#bfdbfe", darkColor: "#93c5fd" }, 51 | ]; 52 | 53 | const trafficData = [ 54 | { name: "Jan", visitors: 2500, conversions: 150, bounces: 1800 }, 55 | { name: "Feb", visitors: 3000, conversions: 210, bounces: 2100 }, 56 | { name: "Mar", visitors: 2800, conversions: 180, bounces: 2000 }, 57 | { name: "Apr", visitors: 3200, conversions: 240, bounces: 2300 }, 58 | { name: "May", visitors: 3800, conversions: 320, bounces: 2500 }, 59 | { name: "Jun", visitors: 4200, conversions: 380, bounces: 2700 }, 60 | ]; 61 | 62 | const trafficSeries = [ 63 | { name: "Visitors", key: "visitors", color: "#d8b4fe", darkColor: "#c084fc" }, 64 | { name: "Conversions", key: "conversions", color: "#bbf7d0", darkColor: "#86efac" }, 65 | { name: "Bounces", key: "bounces", color: "#fda4af", darkColor: "#fb7185" }, 66 | ]; 67 | 68 | export default async function AnalyticsPage() { 69 | const session = await auth(); 70 | const user = await currentUser(); 71 | const userName = user?.firstName || user?.username || "User"; 72 | 73 | return ( 74 |
75 |
76 |
77 |

Analytics Dashboard

78 |

79 | Comprehensive insights and performance metrics 80 |

81 |
82 |
83 | 87 | 91 |
92 |
93 | 94 | {/* Stats Row */} 95 |
96 | 97 | 98 | Total Users 99 |
100 | 101 |
102 |
103 | 104 |
12,548
105 |

106 | 107 | 12.5% from last month 108 |

109 |
110 |
111 | 112 | 113 | Revenue 114 |
115 | 116 |
117 |
118 | 119 |
$25,432
120 |

121 | 122 | 8.2% from last month 123 |

124 |
125 |
126 | 127 | 128 | Conversion Rate 129 |
130 | 131 |
132 |
133 | 134 |
3.6%
135 |

136 | 137 | 1.2% from last month 138 |

139 |
140 |
141 | 142 | 143 | Avg. Session Duration 144 |
145 | 146 |
147 |
148 | 149 |
2m 45s
150 |

151 | 152 | 0.5% from last month 153 |

154 |
155 |
156 |
157 | 158 | {/* Period selector tabs */} 159 | 160 | 161 | Daily 162 | Weekly 163 | Monthly 164 | Yearly 165 | 166 | 167 | 168 | {/* Main Charts */} 169 |
170 | 175 | 181 | 187 | 194 |
195 | 196 | {/* Revenue chart (full width) */} 197 | 198 | 199 | Revenue Overview 200 | 201 | Total monthly revenue with year-over-year comparison 202 | 203 | 204 | 205 |
206 | 211 |
212 |
213 |
214 |
215 | 216 | 217 | 218 | 219 | Daily Analytics 220 | Select a different period to view data 221 | 222 | 223 |

Daily analytics data will appear here

224 |
225 |
226 |
227 | 228 | 229 | 230 | 231 | Weekly Analytics 232 | Select a different period to view data 233 | 234 | 235 |

Weekly analytics data will appear here

236 |
237 |
238 |
239 | 240 | 241 | 242 | 243 | Yearly Analytics 244 | Select a different period to view data 245 | 246 | 247 |

Yearly analytics data will appear here

248 |
249 |
250 |
251 |
252 |
253 | ); 254 | } -------------------------------------------------------------------------------- /src/app/settings/page.tsx: -------------------------------------------------------------------------------- 1 | import { auth, currentUser } from "@clerk/nextjs/server"; 2 | import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from "@/components/ui/card"; 3 | import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs"; 4 | import { Input } from "@/components/ui/input"; 5 | import { Label } from "@/components/ui/label"; 6 | import { Button } from "@/components/ui/button"; 7 | import { Switch } from "@/components/ui/switch"; 8 | import { Save, User, Bell, Lock, Shield, Globe, MonitorSmartphone, Mail } from "lucide-react"; 9 | 10 | export default async function SettingsPage() { 11 | const session = await auth(); 12 | const user = await currentUser(); 13 | const userName = user?.firstName || user?.username || "User"; 14 | 15 | return ( 16 |
17 |
18 |

Settings

19 |

20 | Manage your account settings and preferences 21 |

22 |
23 | 24 | 25 |
26 |
27 | 28 | 29 | 30 | Profile 31 | 32 | 33 | 34 | Account 35 | 36 | 37 | 38 | Notifications 39 | 40 | 41 | 42 | Security 43 | 44 | 45 | 46 | Appearance 47 | 48 | 49 |
50 | 51 |
52 | 53 | 54 | 55 | Profile Information 56 | 57 | Update your profile information and how it appears to others 58 | 59 | 60 | 61 |
62 |
63 | 64 | 65 |
66 | 67 |
68 | 69 | 70 |
71 | 72 |
73 | 74 | 75 |
76 | 77 |
78 | 79 |