create-nextjs-stack 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +60 -0
- package/bin/cli.js +187 -0
- package/package.json +48 -0
- package/templates/admin/.env.example +11 -0
- package/templates/admin/README.md +82 -0
- package/templates/admin/app/(auth)/login/page.tsx +84 -0
- package/templates/admin/app/(dashboard)/[resource]/[id]/page.tsx +45 -0
- package/templates/admin/app/(dashboard)/[resource]/new/page.tsx +32 -0
- package/templates/admin/app/(dashboard)/[resource]/page.tsx +131 -0
- package/templates/admin/app/(dashboard)/categories/[id]/page.tsx +22 -0
- package/templates/admin/app/(dashboard)/categories/new/page.tsx +5 -0
- package/templates/admin/app/(dashboard)/categories/page.tsx +33 -0
- package/templates/admin/app/(dashboard)/clients/[id]/page.tsx +22 -0
- package/templates/admin/app/(dashboard)/clients/new/page.tsx +5 -0
- package/templates/admin/app/(dashboard)/clients/page.tsx +33 -0
- package/templates/admin/app/(dashboard)/dashboard/page.tsx +45 -0
- package/templates/admin/app/(dashboard)/layout.tsx +13 -0
- package/templates/admin/app/(dashboard)/products/[id]/page.tsx +22 -0
- package/templates/admin/app/(dashboard)/products/new/page.tsx +5 -0
- package/templates/admin/app/(dashboard)/products/page.tsx +33 -0
- package/templates/admin/app/(dashboard)/projects/[id]/page.tsx +22 -0
- package/templates/admin/app/(dashboard)/projects/new/page.tsx +5 -0
- package/templates/admin/app/(dashboard)/projects/page.tsx +33 -0
- package/templates/admin/app/(dashboard)/users/[id]/page.tsx +22 -0
- package/templates/admin/app/(dashboard)/users/new/page.tsx +5 -0
- package/templates/admin/app/(dashboard)/users/page.tsx +33 -0
- package/templates/admin/app/actions/resources.ts +46 -0
- package/templates/admin/app/actions/upload.ts +58 -0
- package/templates/admin/app/favicon.ico +0 -0
- package/templates/admin/app/globals.css +23 -0
- package/templates/admin/app/layout.tsx +23 -0
- package/templates/admin/app/page.tsx +5 -0
- package/templates/admin/components/admin/AdminLayoutClient.tsx +22 -0
- package/templates/admin/components/admin/DeleteModal.tsx +90 -0
- package/templates/admin/components/admin/FormLayout.tsx +113 -0
- package/templates/admin/components/admin/ImageUpload.tsx +137 -0
- package/templates/admin/components/admin/ResourceFormClient.tsx +62 -0
- package/templates/admin/components/admin/Sidebar.tsx +74 -0
- package/templates/admin/components/admin/SubmitButton.tsx +34 -0
- package/templates/admin/components/admin/ToastProvider.tsx +8 -0
- package/templates/admin/components/categories/CategoryForm.tsx +24 -0
- package/templates/admin/components/categories/CategoryList.tsx +113 -0
- package/templates/admin/components/clients/ClientForm.tsx +24 -0
- package/templates/admin/components/clients/ClientList.tsx +113 -0
- package/templates/admin/components/products/ProductForm.tsx +24 -0
- package/templates/admin/components/products/ProductList.tsx +117 -0
- package/templates/admin/components/projects/ProjectForm.tsx +24 -0
- package/templates/admin/components/projects/ProjectList.tsx +121 -0
- package/templates/admin/components/users/UserForm.tsx +39 -0
- package/templates/admin/components/users/UserList.tsx +101 -0
- package/templates/admin/config/resources.ts +123 -0
- package/templates/admin/eslint.config.mjs +18 -0
- package/templates/admin/hooks/useResource.ts +86 -0
- package/templates/admin/lib/services/base.service.ts +106 -0
- package/templates/admin/lib/services/categories.service.ts +7 -0
- package/templates/admin/lib/services/clients.service.ts +7 -0
- package/templates/admin/lib/services/index.ts +27 -0
- package/templates/admin/lib/services/products.service.ts +9 -0
- package/templates/admin/lib/services/projects.service.ts +22 -0
- package/templates/admin/lib/services/resource.service.ts +26 -0
- package/templates/admin/lib/services/users.service.ts +9 -0
- package/templates/admin/lib/supabase/client.ts +9 -0
- package/templates/admin/lib/supabase/middleware.ts +57 -0
- package/templates/admin/lib/supabase/server.ts +29 -0
- package/templates/admin/middleware.ts +15 -0
- package/templates/admin/next.config.ts +10 -0
- package/templates/admin/package-lock.json +6768 -0
- package/templates/admin/package.json +33 -0
- package/templates/admin/postcss.config.mjs +7 -0
- package/templates/admin/public/file.svg +1 -0
- package/templates/admin/public/globe.svg +1 -0
- package/templates/admin/public/next.svg +1 -0
- package/templates/admin/public/vercel.svg +1 -0
- package/templates/admin/public/window.svg +1 -0
- package/templates/admin/supabase_mock_data.sql +57 -0
- package/templates/admin/supabase_schema.sql +93 -0
- package/templates/admin/tsconfig.json +34 -0
- package/templates/web/.env.example +21 -0
- package/templates/web/README.md +129 -0
- package/templates/web/components.json +22 -0
- package/templates/web/eslint.config.mjs +25 -0
- package/templates/web/next.config.ts +25 -0
- package/templates/web/package-lock.json +6778 -0
- package/templates/web/package.json +45 -0
- package/templates/web/postcss.config.mjs +5 -0
- package/templates/web/src/app/api/contact/route.ts +181 -0
- package/templates/web/src/app/api/revalidate/route.ts +95 -0
- package/templates/web/src/app/error.tsx +28 -0
- package/templates/web/src/app/globals.css +838 -0
- package/templates/web/src/app/layout.tsx +126 -0
- package/templates/web/src/app/loading.tsx +60 -0
- package/templates/web/src/app/not-found.tsx +68 -0
- package/templates/web/src/app/page.tsx +106 -0
- package/templates/web/src/app/robots.ts +12 -0
- package/templates/web/src/app/sitemap.ts +66 -0
- package/templates/web/src/components/home/StatsGrid.tsx +89 -0
- package/templates/web/src/hooks/useIntersectionObserver.ts +39 -0
- package/templates/web/src/lib/providers/StoreProvider.tsx +12 -0
- package/templates/web/src/lib/seo/index.ts +4 -0
- package/templates/web/src/lib/seo/metadata.ts +103 -0
- package/templates/web/src/lib/seo/seo.config.ts +161 -0
- package/templates/web/src/lib/seo/seo.types.ts +76 -0
- package/templates/web/src/lib/services/categories.service.ts +38 -0
- package/templates/web/src/lib/services/categoryService.ts +251 -0
- package/templates/web/src/lib/services/clientService.ts +132 -0
- package/templates/web/src/lib/services/clients.service.ts +20 -0
- package/templates/web/src/lib/services/productService.ts +261 -0
- package/templates/web/src/lib/services/products.service.ts +38 -0
- package/templates/web/src/lib/services/projectService.ts +234 -0
- package/templates/web/src/lib/services/projects.service.ts +38 -0
- package/templates/web/src/lib/services/users.service.ts +20 -0
- package/templates/web/src/lib/supabase/client.ts +42 -0
- package/templates/web/src/lib/supabase/constants.ts +25 -0
- package/templates/web/src/lib/supabase/server.ts +29 -0
- package/templates/web/src/lib/supabase/types.ts +112 -0
- package/templates/web/src/lib/utils/cache.ts +98 -0
- package/templates/web/src/lib/utils/rate-limiter.ts +102 -0
- package/templates/web/src/store/actions/index.ts +2 -0
- package/templates/web/src/store/index.ts +13 -0
- package/templates/web/src/store/reducers/index.ts +13 -0
- package/templates/web/src/store/types/index.ts +2 -0
- package/templates/web/tsconfig.json +41 -0
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
import type { Metadata, Viewport } from "next";
|
|
2
|
+
import {
|
|
3
|
+
Geist,
|
|
4
|
+
Geist_Mono,
|
|
5
|
+
Inter,
|
|
6
|
+
Outfit,
|
|
7
|
+
Raleway,
|
|
8
|
+
Roboto,
|
|
9
|
+
Jersey_10,
|
|
10
|
+
} from "next/font/google";
|
|
11
|
+
import "@/app/globals.css";
|
|
12
|
+
import StoreProvider from "@/lib/providers/StoreProvider";
|
|
13
|
+
import { GoogleAnalytics } from "@next/third-parties/google";
|
|
14
|
+
|
|
15
|
+
const geistSans = Geist({
|
|
16
|
+
variable: "--font-geist-sans",
|
|
17
|
+
subsets: ["latin"],
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const geistMono = Geist_Mono({
|
|
21
|
+
variable: "--font-geist-mono",
|
|
22
|
+
subsets: ["latin"],
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const inter = Inter({
|
|
26
|
+
variable: "--font-inter",
|
|
27
|
+
subsets: ["latin"],
|
|
28
|
+
display: "swap",
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
const roboto = Roboto({
|
|
32
|
+
variable: "--font-roboto",
|
|
33
|
+
subsets: ["latin"],
|
|
34
|
+
weight: ["100", "300", "400", "500", "700", "900"],
|
|
35
|
+
display: "swap",
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
const outfit = Outfit({
|
|
39
|
+
variable: "--font-outfit",
|
|
40
|
+
subsets: ["latin"],
|
|
41
|
+
display: "swap",
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
const raleway = Raleway({
|
|
45
|
+
variable: "--font-raleway",
|
|
46
|
+
subsets: ["latin"],
|
|
47
|
+
display: "swap",
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
const jersey10 = Jersey_10({
|
|
51
|
+
variable: "--font-jersey-10",
|
|
52
|
+
subsets: ["latin"],
|
|
53
|
+
weight: "400",
|
|
54
|
+
display: "swap",
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
export const viewport: Viewport = {
|
|
58
|
+
themeColor: [
|
|
59
|
+
{ media: "(prefers-color-scheme: light)", color: "#ffffff" },
|
|
60
|
+
{ media: "(prefers-color-scheme: dark)", color: "#000000" },
|
|
61
|
+
],
|
|
62
|
+
width: "device-width",
|
|
63
|
+
initialScale: 1,
|
|
64
|
+
maximumScale: 1,
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export const metadata: Metadata = {
|
|
68
|
+
metadataBase: new URL("https://create-nextjs-stack.vercel.app"),
|
|
69
|
+
title: {
|
|
70
|
+
default: "Next.js Enterprise Starter",
|
|
71
|
+
template: "%s | Next.js Enterprise Starter",
|
|
72
|
+
},
|
|
73
|
+
description: "A production-ready Next.js starter template with Supabase, Tailwind CSS, and more.",
|
|
74
|
+
manifest: "/manifest.json",
|
|
75
|
+
appleWebApp: {
|
|
76
|
+
capable: true,
|
|
77
|
+
statusBarStyle: "default",
|
|
78
|
+
title: "Next.js Starter",
|
|
79
|
+
},
|
|
80
|
+
icons: {
|
|
81
|
+
icon: [
|
|
82
|
+
{ url: "/favicon.png", sizes: "32x32" },
|
|
83
|
+
{ url: "/favicon.png", sizes: "16x16" },
|
|
84
|
+
],
|
|
85
|
+
apple: {
|
|
86
|
+
url: "/favicon.png",
|
|
87
|
+
sizes: "180x180",
|
|
88
|
+
},
|
|
89
|
+
shortcut: "/favicon.png",
|
|
90
|
+
},
|
|
91
|
+
alternates: {
|
|
92
|
+
canonical: "https://create-nextjs-stack.vercel.app",
|
|
93
|
+
},
|
|
94
|
+
openGraph: {
|
|
95
|
+
locale: "en_US",
|
|
96
|
+
type: "website",
|
|
97
|
+
siteName: "Next.js Enterprise Starter",
|
|
98
|
+
images: [
|
|
99
|
+
{
|
|
100
|
+
url: "/logo.png",
|
|
101
|
+
width: 1200,
|
|
102
|
+
height: 630,
|
|
103
|
+
alt: "Next.js Enterprise Starter",
|
|
104
|
+
},
|
|
105
|
+
],
|
|
106
|
+
},
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
export default function RootLayout({
|
|
110
|
+
children,
|
|
111
|
+
}: {
|
|
112
|
+
children: React.ReactNode;
|
|
113
|
+
}) {
|
|
114
|
+
return (
|
|
115
|
+
<html lang="en">
|
|
116
|
+
<body
|
|
117
|
+
className={`${geistSans.variable} ${geistMono.variable} ${inter.variable} ${outfit.variable} ${roboto.variable} ${raleway.variable} ${jersey10.variable} antialiased`}
|
|
118
|
+
>
|
|
119
|
+
<StoreProvider>
|
|
120
|
+
{children}
|
|
121
|
+
<GoogleAnalytics gaId="G-XYZ1234567" />
|
|
122
|
+
</StoreProvider>
|
|
123
|
+
</body>
|
|
124
|
+
</html>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import { motion } from "framer-motion";
|
|
4
|
+
|
|
5
|
+
export default function Loading() {
|
|
6
|
+
return (
|
|
7
|
+
<div className="flex flex-col items-center justify-center min-h-screen bg-black overflow-hidden relative">
|
|
8
|
+
{/* Background Grid Effect */}
|
|
9
|
+
<div className="absolute inset-0 bg-[linear-gradient(rgba(255,255,255,0.02)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.02)_1px,transparent_1px)] bg-[size:40px_40px] [mask-image:radial-gradient(ellipse_60%_60%_at_50%_50%,black,transparent)]" />
|
|
10
|
+
|
|
11
|
+
<motion.div
|
|
12
|
+
className="relative z-10 flex flex-col items-center gap-8"
|
|
13
|
+
initial={{ opacity: 0 }}
|
|
14
|
+
animate={{ opacity: 1 }}
|
|
15
|
+
transition={{ duration: 0.5 }}
|
|
16
|
+
>
|
|
17
|
+
{/* Tech Spinner */}
|
|
18
|
+
<div className="relative w-24 h-24">
|
|
19
|
+
<motion.div
|
|
20
|
+
className="absolute inset-0 border-4 border-red-600/30 rounded-full"
|
|
21
|
+
animate={{ scale: [1, 1.1, 1], opacity: [0.3, 0.6, 0.3] }}
|
|
22
|
+
transition={{ duration: 2, repeat: Infinity, ease: "easeInOut" }}
|
|
23
|
+
/>
|
|
24
|
+
<motion.div
|
|
25
|
+
className="absolute inset-0 border-4 border-t-red-600 border-r-transparent border-b-transparent border-l-transparent rounded-full"
|
|
26
|
+
animate={{ rotate: 360 }}
|
|
27
|
+
transition={{ duration: 1, repeat: Infinity, ease: "linear" }}
|
|
28
|
+
/>
|
|
29
|
+
<div className="absolute inset-4 border-2 border-white/10 rounded-full" />
|
|
30
|
+
<motion.div
|
|
31
|
+
className="absolute inset-[30%] bg-red-600 rounded-full"
|
|
32
|
+
animate={{ scale: [0.8, 1.2, 0.8], opacity: [0.8, 1, 0.8] }}
|
|
33
|
+
transition={{ duration: 1.5, repeat: Infinity }}
|
|
34
|
+
/>
|
|
35
|
+
</div>
|
|
36
|
+
|
|
37
|
+
{/* Text */}
|
|
38
|
+
<div className="flex flex-col items-center gap-2">
|
|
39
|
+
<motion.span
|
|
40
|
+
className="font-jersey-10 text-2xl text-white tracking-[0.2em]"
|
|
41
|
+
animate={{ opacity: [1, 0.5, 1] }}
|
|
42
|
+
transition={{ duration: 1.5, repeat: Infinity }}
|
|
43
|
+
>
|
|
44
|
+
LOADING
|
|
45
|
+
</motion.span>
|
|
46
|
+
<div className="flex gap-1 h-1">
|
|
47
|
+
{[...Array(3)].map((_, i) => (
|
|
48
|
+
<motion.div
|
|
49
|
+
key={i}
|
|
50
|
+
className="w-8 h-full bg-red-600/50"
|
|
51
|
+
animate={{ scaleX: [0, 1, 0], opacity: [0, 1, 0] }}
|
|
52
|
+
transition={{ duration: 1, delay: i * 0.2, repeat: Infinity }}
|
|
53
|
+
/>
|
|
54
|
+
))}
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
</motion.div>
|
|
58
|
+
</div>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
import Link from 'next/link';
|
|
4
|
+
import { motion } from 'framer-motion';
|
|
5
|
+
|
|
6
|
+
export default function NotFound() {
|
|
7
|
+
return (
|
|
8
|
+
<div className="flex flex-col items-center justify-center min-h-screen text-center px-4 bg-black text-white relative overflow-hidden">
|
|
9
|
+
{/* Background Grid Effect */}
|
|
10
|
+
<div className="absolute inset-0 bg-[linear-gradient(rgba(255,255,255,0.02)_1px,transparent_1px),linear-gradient(90deg,rgba(255,255,255,0.02)_1px,transparent_1px)] bg-[size:40px_40px] [mask-image:radial-gradient(ellipse_60%_60%_at_50%_50%,black,transparent)] pointer-events-none" />
|
|
11
|
+
|
|
12
|
+
<div className="relative z-10">
|
|
13
|
+
<motion.div
|
|
14
|
+
initial={{ opacity: 0, scale: 0.8 }}
|
|
15
|
+
animate={{ opacity: 1, scale: 1 }}
|
|
16
|
+
transition={{ duration: 0.5 }}
|
|
17
|
+
className="relative"
|
|
18
|
+
>
|
|
19
|
+
<h1 className="text-[150px] md:text-[200px] font-jersey-10 font-bold text-red-600 leading-none select-none relative">
|
|
20
|
+
<span className="relative z-10">404</span>
|
|
21
|
+
<motion.span
|
|
22
|
+
className="absolute top-0 left-0 text-red-600/50 z-0"
|
|
23
|
+
animate={{ x: [-2, 2, -2], opacity: [0.5, 0.8, 0.5] }}
|
|
24
|
+
transition={{ duration: 0.1, repeat: Infinity, repeatType: "mirror" }}
|
|
25
|
+
>
|
|
26
|
+
404
|
|
27
|
+
</motion.span>
|
|
28
|
+
<motion.span
|
|
29
|
+
className="absolute top-0 left-0 text-white/20 z-0"
|
|
30
|
+
animate={{ x: [2, -2, 2], opacity: [0.3, 0.6, 0.3] }}
|
|
31
|
+
transition={{ duration: 0.15, repeat: Infinity, repeatType: "mirror" }}
|
|
32
|
+
>
|
|
33
|
+
404
|
|
34
|
+
</motion.span>
|
|
35
|
+
</h1>
|
|
36
|
+
</motion.div>
|
|
37
|
+
|
|
38
|
+
<motion.div
|
|
39
|
+
initial={{ opacity: 0, y: 20 }}
|
|
40
|
+
animate={{ opacity: 1, y: 0 }}
|
|
41
|
+
transition={{ delay: 0.2, duration: 0.5 }}
|
|
42
|
+
>
|
|
43
|
+
<div className="flex items-center justify-center gap-4 mb-6">
|
|
44
|
+
<div className="h-[1px] w-12 bg-red-600/50" />
|
|
45
|
+
<h2 className="text-xl md:text-2xl font-outfit font-bold tracking-widest uppercase text-white/80">
|
|
46
|
+
System Error
|
|
47
|
+
</h2>
|
|
48
|
+
<div className="h-[1px] w-12 bg-red-600/50" />
|
|
49
|
+
</div>
|
|
50
|
+
|
|
51
|
+
<p className="text-white/60 mb-10 max-w-md mx-auto font-roboto">
|
|
52
|
+
An error occurred
|
|
53
|
+
</p>
|
|
54
|
+
|
|
55
|
+
<Link
|
|
56
|
+
href="/"
|
|
57
|
+
className="group relative inline-flex items-center justify-center px-8 py-4 overflow-hidden font-bold text-white transition-all duration-300 bg-red-600 rounded-sm hover:bg-red-700 focus:outline-none focus:ring focus:ring-red-400 focus:ring-offset-2 focus:ring-offset-gray-900"
|
|
58
|
+
>
|
|
59
|
+
<span className="absolute inset-0 w-full h-full -mt-1 rounded-lg opacity-30 bg-gradient-to-b from-transparent via-transparent to-black"></span>
|
|
60
|
+
<span className="relative font-outfit tracking-wider">
|
|
61
|
+
Back to Home Page
|
|
62
|
+
</span>
|
|
63
|
+
</Link>
|
|
64
|
+
</motion.div>
|
|
65
|
+
</div>
|
|
66
|
+
</div>
|
|
67
|
+
);
|
|
68
|
+
}
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import StatsGrid from "@/components/home/StatsGrid";
|
|
2
|
+
import { Github, Globe } from "lucide-react";
|
|
3
|
+
|
|
4
|
+
export default function Home() {
|
|
5
|
+
return (
|
|
6
|
+
<main className="min-h-screen bg-black text-white">
|
|
7
|
+
{/* Hero Section */}
|
|
8
|
+
<section className="relative py-24 px-6 md:px-12 lg:px-24 flex flex-col items-center text-center">
|
|
9
|
+
<div className="absolute inset-0 bg-gradient-to-b from-blue-900/20 to-transparent pointer-events-none" />
|
|
10
|
+
|
|
11
|
+
<h1 className="text-5xl md:text-7xl font-bold tracking-tight mb-6 bg-gradient-to-r from-white to-zinc-500 bg-clip-text text-transparent">
|
|
12
|
+
Next.js Enterprise Stack
|
|
13
|
+
</h1>
|
|
14
|
+
|
|
15
|
+
<p className="text-lg md:text-xl text-zinc-400 max-w-2xl mb-12">
|
|
16
|
+
A production-ready starter template featuring Next.js 15, Tailwind CSS 4, Supabase, and a fully integrated Admin Dashboard.
|
|
17
|
+
</p>
|
|
18
|
+
|
|
19
|
+
<div className="flex flex-col md:flex-row gap-4">
|
|
20
|
+
<a
|
|
21
|
+
href="https://github.com/burakz-cn/create-nextjs-stack"
|
|
22
|
+
target="_blank"
|
|
23
|
+
className="px-8 py-3 bg-white text-black font-semibold rounded-full hover:bg-zinc-200 transition"
|
|
24
|
+
>
|
|
25
|
+
Get Started
|
|
26
|
+
</a>
|
|
27
|
+
<a
|
|
28
|
+
href="#"
|
|
29
|
+
className="px-8 py-3 border border-zinc-700 text-white font-semibold rounded-full hover:bg-zinc-800 transition"
|
|
30
|
+
>
|
|
31
|
+
Documentation
|
|
32
|
+
</a>
|
|
33
|
+
</div>
|
|
34
|
+
</section>
|
|
35
|
+
|
|
36
|
+
{/* Stats Section */}
|
|
37
|
+
<section className="py-24 px-6 md:px-12 lg:px-24 bg-zinc-950/50 border-t border-zinc-900">
|
|
38
|
+
<div className="max-w-7xl mx-auto">
|
|
39
|
+
<div className="flex flex-col md:flex-row justify-between items-end mb-12">
|
|
40
|
+
<div>
|
|
41
|
+
<h2 className="text-3xl font-bold mb-2">Live Database Stats</h2>
|
|
42
|
+
<p className="text-zinc-500">Real-time data fetched from your Supabase instance.</p>
|
|
43
|
+
</div>
|
|
44
|
+
<div className="hidden md:block">
|
|
45
|
+
<span className="flex items-center text-sm text-green-500">
|
|
46
|
+
<span className="w-2 h-2 bg-green-500 rounded-full mr-2 animate-pulse" />
|
|
47
|
+
System Online
|
|
48
|
+
</span>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<StatsGrid />
|
|
53
|
+
</div>
|
|
54
|
+
</section>
|
|
55
|
+
|
|
56
|
+
{/* Features Grid (Static for demo) */}
|
|
57
|
+
<section className="py-24 px-6 md:px-12 lg:px-24">
|
|
58
|
+
<div className="max-w-7xl mx-auto grid grid-cols-1 md:grid-cols-3 gap-12">
|
|
59
|
+
<div className="space-y-4">
|
|
60
|
+
<h3 className="text-xl font-bold">Type Safe</h3>
|
|
61
|
+
<p className="text-zinc-400">End-to-end type safety with TypeScript, Zod, and generated Supabase types.</p>
|
|
62
|
+
</div>
|
|
63
|
+
<div className="space-y-4">
|
|
64
|
+
<h3 className="text-xl font-bold">Admin Panel</h3>
|
|
65
|
+
<p className="text-zinc-400">Fully functional admin dashboard with CRUD operations, auth, and resource management.</p>
|
|
66
|
+
</div>
|
|
67
|
+
<div className="space-y-4">
|
|
68
|
+
<h3 className="text-xl font-bold">Modern Stack</h3>
|
|
69
|
+
<p className="text-zinc-400">Built with the latest Next.js 15 App Router, React 19, and Tailwind CSS 4.</p>
|
|
70
|
+
</div>
|
|
71
|
+
</div>
|
|
72
|
+
</section>
|
|
73
|
+
|
|
74
|
+
{/* Footer / Credits */}
|
|
75
|
+
<footer className="py-12 px-6 md:px-12 lg:px-24 border-t border-zinc-900 bg-zinc-950">
|
|
76
|
+
<div className="max-w-7xl mx-auto flex flex-col md:flex-row justify-between items-center gap-6">
|
|
77
|
+
<p className="text-zinc-500 text-sm">
|
|
78
|
+
© {new Date().getFullYear()} Next.js Enterprise Stack. All rights reserved.
|
|
79
|
+
</p>
|
|
80
|
+
|
|
81
|
+
<div className="flex items-center gap-6">
|
|
82
|
+
<a
|
|
83
|
+
href="https://github.com/mburakaltiparmak"
|
|
84
|
+
target="_blank"
|
|
85
|
+
rel="noopener noreferrer"
|
|
86
|
+
className="group flex items-center gap-2 text-zinc-400 hover:text-white transition-colors"
|
|
87
|
+
>
|
|
88
|
+
<Github className="w-5 h-5" />
|
|
89
|
+
<span className="text-sm font-medium">mburakaltiparmak</span>
|
|
90
|
+
</a>
|
|
91
|
+
|
|
92
|
+
<a
|
|
93
|
+
href="https://burakaltiparmak.site"
|
|
94
|
+
target="_blank"
|
|
95
|
+
rel="noopener noreferrer"
|
|
96
|
+
className="group flex items-center gap-2 text-zinc-400 hover:text-white transition-colors"
|
|
97
|
+
>
|
|
98
|
+
<Globe className="w-5 h-5" />
|
|
99
|
+
<span className="text-sm font-medium">burakaltiparmak.site</span>
|
|
100
|
+
</a>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</footer>
|
|
104
|
+
</main>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { MetadataRoute } from 'next';
|
|
2
|
+
|
|
3
|
+
export default function robots(): MetadataRoute.Robots {
|
|
4
|
+
return {
|
|
5
|
+
rules: {
|
|
6
|
+
userAgent: '*',
|
|
7
|
+
allow: '/',
|
|
8
|
+
disallow: ['/api/', '/_next/'],
|
|
9
|
+
},
|
|
10
|
+
sitemap: 'https://www.DOMAIN_PLACEHOLDER.com/sitemap.xml',
|
|
11
|
+
};
|
|
12
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { MetadataRoute } from 'next';
|
|
2
|
+
import { projectService } from '@/lib/services/projectService';
|
|
3
|
+
import { productService } from '@/lib/services/productService';
|
|
4
|
+
|
|
5
|
+
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|
6
|
+
const baseUrl = 'https://www.DOMAIN_PLACEHOLDER.com';
|
|
7
|
+
|
|
8
|
+
// Static pages - CUSTOMIZE THESE ROUTES FOR YOUR PROJECT
|
|
9
|
+
const routes = [
|
|
10
|
+
'',
|
|
11
|
+
'/about',
|
|
12
|
+
'/products',
|
|
13
|
+
'/innovation',
|
|
14
|
+
'/services',
|
|
15
|
+
'/investors',
|
|
16
|
+
'/references',
|
|
17
|
+
'/contact',
|
|
18
|
+
'/impressum',
|
|
19
|
+
'/datenschutz',
|
|
20
|
+
'/agb'
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
const sitemap: MetadataRoute.Sitemap = [];
|
|
24
|
+
|
|
25
|
+
// Generate entries for each route
|
|
26
|
+
routes.forEach((route) => {
|
|
27
|
+
sitemap.push({
|
|
28
|
+
url: `${baseUrl}${route}`,
|
|
29
|
+
lastModified: new Date(),
|
|
30
|
+
changeFrequency: route === '' ? 'daily' : 'weekly',
|
|
31
|
+
priority: route === '' ? 1.0 : (route === '/contact' ? 0.9 : 0.8),
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
// Dynamic Projects
|
|
36
|
+
try {
|
|
37
|
+
const projectSlugs = await projectService.getAllSlugs();
|
|
38
|
+
projectSlugs.forEach((slug) => {
|
|
39
|
+
sitemap.push({
|
|
40
|
+
url: `${baseUrl}/references/${slug}`,
|
|
41
|
+
lastModified: new Date(),
|
|
42
|
+
changeFrequency: 'weekly',
|
|
43
|
+
priority: 0.7,
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error('[Sitemap] Error fetching project slugs:', error);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Dynamic Products
|
|
51
|
+
try {
|
|
52
|
+
const productSlugs = await productService.getAllSlugs();
|
|
53
|
+
productSlugs.forEach((slug) => {
|
|
54
|
+
sitemap.push({
|
|
55
|
+
url: `${baseUrl}/products/${slug}`,
|
|
56
|
+
lastModified: new Date(),
|
|
57
|
+
changeFrequency: 'weekly',
|
|
58
|
+
priority: 0.8,
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
} catch (error) {
|
|
62
|
+
console.error('[Sitemap] Error fetching product slugs:', error);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
return sitemap;
|
|
66
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { CategoryService } from "@/lib/services/categories.service";
|
|
2
|
+
import { ProductService } from "@/lib/services/products.service";
|
|
3
|
+
import { ProjectService } from "@/lib/services/projects.service";
|
|
4
|
+
import { ClientService } from "@/lib/services/clients.service";
|
|
5
|
+
import { UserService } from "@/lib/services/users.service";
|
|
6
|
+
import {
|
|
7
|
+
Layers,
|
|
8
|
+
Package,
|
|
9
|
+
Briefcase,
|
|
10
|
+
Users,
|
|
11
|
+
Settings
|
|
12
|
+
} from "lucide-react";
|
|
13
|
+
|
|
14
|
+
export default async function StatsGrid() {
|
|
15
|
+
const [
|
|
16
|
+
categories,
|
|
17
|
+
products,
|
|
18
|
+
projects,
|
|
19
|
+
clients,
|
|
20
|
+
users
|
|
21
|
+
] = await Promise.all([
|
|
22
|
+
CategoryService.getAll(),
|
|
23
|
+
ProductService.getAll(),
|
|
24
|
+
ProjectService.getAll(),
|
|
25
|
+
ClientService.getAll(),
|
|
26
|
+
UserService.getAll()
|
|
27
|
+
]);
|
|
28
|
+
|
|
29
|
+
const stats = [
|
|
30
|
+
{
|
|
31
|
+
label: "Total Products",
|
|
32
|
+
value: products.length,
|
|
33
|
+
icon: Package,
|
|
34
|
+
color: "bg-blue-500",
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
label: "Total Categories",
|
|
38
|
+
value: categories.length,
|
|
39
|
+
icon: Layers,
|
|
40
|
+
color: "bg-purple-500",
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
label: "Total Projects",
|
|
44
|
+
value: projects.length,
|
|
45
|
+
icon: Briefcase,
|
|
46
|
+
color: "bg-indigo-500",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
label: "Total Clients",
|
|
50
|
+
value: clients.length,
|
|
51
|
+
icon: Users,
|
|
52
|
+
color: "bg-pink-500",
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
label: "Registered Users",
|
|
56
|
+
value: users.length,
|
|
57
|
+
icon: Settings,
|
|
58
|
+
color: "bg-orange-500",
|
|
59
|
+
},
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
return (
|
|
63
|
+
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-6">
|
|
64
|
+
{stats.map((stat) => {
|
|
65
|
+
const Icon = stat.icon;
|
|
66
|
+
return (
|
|
67
|
+
<div
|
|
68
|
+
key={stat.label}
|
|
69
|
+
className="bg-zinc-900 border border-zinc-800 rounded-xl p-6 transition hover:border-zinc-700 hover:bg-zinc-800/50"
|
|
70
|
+
>
|
|
71
|
+
<div className="flex items-center gap-4">
|
|
72
|
+
<div className={`p-3 rounded-lg ${stat.color} bg-opacity-10`}>
|
|
73
|
+
<Icon className={`w-6 h-6 ${stat.color.replace("bg-", "text-")}`} />
|
|
74
|
+
</div>
|
|
75
|
+
<div>
|
|
76
|
+
<p className="text-sm font-medium text-zinc-400">
|
|
77
|
+
{stat.label}
|
|
78
|
+
</p>
|
|
79
|
+
<p className="text-2xl font-bold text-white mt-1">
|
|
80
|
+
{stat.value}
|
|
81
|
+
</p>
|
|
82
|
+
</div>
|
|
83
|
+
</div>
|
|
84
|
+
</div>
|
|
85
|
+
);
|
|
86
|
+
})}
|
|
87
|
+
</div>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { useState, useEffect, RefObject } from 'react';
|
|
2
|
+
|
|
3
|
+
interface UseIntersectionObserverOptions {
|
|
4
|
+
threshold?: number;
|
|
5
|
+
rootMargin?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export const useIntersectionObserver = (
|
|
9
|
+
ref: RefObject<Element | null>, // Modified to accept null which is common with useRef
|
|
10
|
+
options: UseIntersectionObserverOptions = {}
|
|
11
|
+
): boolean => {
|
|
12
|
+
const [isVisible, setIsVisible] = useState(false);
|
|
13
|
+
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const observer = new IntersectionObserver(
|
|
16
|
+
([entry]) => {
|
|
17
|
+
if (entry.isIntersecting) {
|
|
18
|
+
setIsVisible(true);
|
|
19
|
+
observer.disconnect(); // Load once, then stop observing
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
{
|
|
23
|
+
threshold: options.threshold || 0,
|
|
24
|
+
rootMargin: options.rootMargin || '100px' // Default: load 100px before appearing
|
|
25
|
+
}
|
|
26
|
+
);
|
|
27
|
+
|
|
28
|
+
const currentRef = ref.current;
|
|
29
|
+
if (currentRef) {
|
|
30
|
+
observer.observe(currentRef);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
return () => {
|
|
34
|
+
observer.disconnect();
|
|
35
|
+
};
|
|
36
|
+
}, [ref, options.threshold, options.rootMargin]);
|
|
37
|
+
|
|
38
|
+
return isVisible;
|
|
39
|
+
};
|