create-nextjs-stack 0.1.0 → 0.1.2
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/README.md +502 -31
- package/package.json +1 -1
- package/templates/admin/app/(dashboard)/[resource]/[id]/page.tsx +6 -5
- package/templates/admin/app/(dashboard)/[resource]/new/page.tsx +11 -12
- package/templates/admin/app/(dashboard)/[resource]/page.tsx +4 -3
- package/templates/admin/app/actions/upload.ts +0 -7
- package/templates/admin/app/globals.css +112 -14
- package/templates/admin/components/admin/Sidebar.tsx +2 -5
- package/templates/admin/components.json +22 -0
- package/templates/admin/hooks/useResource.ts +3 -0
- package/templates/admin/lib/services/resource.service.ts +2 -7
- package/templates/admin/lib/supabase/client.ts +8 -4
- package/templates/admin/lib/supabase/server.ts +1 -1
- package/templates/admin/lib/utils.ts +6 -0
- package/templates/admin/middleware.ts +1 -3
- package/templates/admin/next.config.ts +10 -2
- package/templates/admin/package-lock.json +13 -1
- package/templates/admin/package.json +3 -1
- package/templates/web/.env.example +5 -1
- package/templates/web/package-lock.json +0 -32
- package/templates/web/package.json +2 -0
- package/templates/web/postcss.config.mjs +3 -1
- package/templates/web/src/app/api/revalidate/route.ts +46 -87
- package/templates/web/src/app/globals.css +1 -13
- package/templates/web/src/app/layout.tsx +4 -46
- package/templates/web/src/app/robots.ts +1 -1
- package/templates/web/src/app/sitemap.ts +27 -31
- package/templates/web/src/lib/seo/metadata.ts +5 -5
- package/templates/web/src/lib/seo/seo.config.ts +55 -59
- package/templates/web/src/lib/seo/seo.types.ts +1 -7
- package/templates/web/src/lib/services/categories.service.ts +3 -3
- package/templates/web/src/lib/services/clients.service.ts +2 -2
- package/templates/web/src/lib/services/products.service.ts +3 -3
- package/templates/web/src/lib/services/projects.service.ts +3 -3
- package/templates/web/src/lib/services/users.service.ts +2 -2
- package/templates/web/src/lib/supabase/client.ts +1 -1
- package/templates/web/src/lib/supabase/server.ts +1 -1
- package/templates/web/src/store/index.ts +4 -9
- package/templates/admin/app/(dashboard)/categories/[id]/page.tsx +0 -22
- package/templates/admin/app/(dashboard)/categories/new/page.tsx +0 -5
- package/templates/admin/app/(dashboard)/categories/page.tsx +0 -33
- package/templates/admin/app/(dashboard)/clients/[id]/page.tsx +0 -22
- package/templates/admin/app/(dashboard)/clients/new/page.tsx +0 -5
- package/templates/admin/app/(dashboard)/clients/page.tsx +0 -33
- package/templates/admin/app/(dashboard)/products/[id]/page.tsx +0 -22
- package/templates/admin/app/(dashboard)/products/new/page.tsx +0 -5
- package/templates/admin/app/(dashboard)/products/page.tsx +0 -33
- package/templates/admin/app/(dashboard)/projects/[id]/page.tsx +0 -22
- package/templates/admin/app/(dashboard)/projects/new/page.tsx +0 -5
- package/templates/admin/app/(dashboard)/projects/page.tsx +0 -33
- package/templates/admin/app/(dashboard)/users/[id]/page.tsx +0 -22
- package/templates/admin/app/(dashboard)/users/new/page.tsx +0 -5
- package/templates/admin/app/(dashboard)/users/page.tsx +0 -33
- package/templates/admin/components/categories/CategoryForm.tsx +0 -24
- package/templates/admin/components/categories/CategoryList.tsx +0 -113
- package/templates/admin/components/clients/ClientForm.tsx +0 -24
- package/templates/admin/components/clients/ClientList.tsx +0 -113
- package/templates/admin/components/products/ProductForm.tsx +0 -24
- package/templates/admin/components/products/ProductList.tsx +0 -117
- package/templates/admin/components/projects/ProjectForm.tsx +0 -24
- package/templates/admin/components/projects/ProjectList.tsx +0 -121
- package/templates/admin/components/users/UserForm.tsx +0 -39
- package/templates/admin/components/users/UserList.tsx +0 -101
- package/templates/web/src/lib/services/categoryService.ts +0 -251
- package/templates/web/src/lib/services/clientService.ts +0 -132
- package/templates/web/src/lib/services/productService.ts +0 -261
- package/templates/web/src/lib/services/projectService.ts +0 -234
- package/templates/web/src/lib/utils/cache.ts +0 -98
- package/templates/web/src/lib/utils/rate-limiter.ts +0 -102
|
@@ -30,13 +30,6 @@ export async function uploadImage(formData: FormData) {
|
|
|
30
30
|
return { error: "No file provided" };
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
// Debug: Log config state (safe)
|
|
34
|
-
const config = cloudinary.config();
|
|
35
|
-
console.log("Active Cloudinary Config:", {
|
|
36
|
-
cloud_name: config.cloud_name,
|
|
37
|
-
api_key: config.api_key ? "HIDDEN (Set)" : "MISSING",
|
|
38
|
-
api_secret: config.api_secret ? "HIDDEN (Set)" : "MISSING",
|
|
39
|
-
});
|
|
40
33
|
|
|
41
34
|
try {
|
|
42
35
|
const arrayBuffer = await file.arrayBuffer();
|
|
@@ -1,23 +1,121 @@
|
|
|
1
1
|
@import "tailwindcss";
|
|
2
2
|
|
|
3
|
+
@custom-variant dark (&:is(.dark *));
|
|
4
|
+
|
|
5
|
+
@theme inline {
|
|
6
|
+
--color-background: var(--background);
|
|
7
|
+
--color-foreground: var(--foreground);
|
|
8
|
+
--font-sans: var(--font-geist-sans);
|
|
9
|
+
--font-mono: var(--font-geist-mono);
|
|
10
|
+
--color-sidebar-ring: var(--sidebar-ring);
|
|
11
|
+
--color-sidebar-border: var(--sidebar-border);
|
|
12
|
+
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
13
|
+
--color-sidebar-accent: var(--sidebar-accent);
|
|
14
|
+
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
15
|
+
--color-sidebar-primary: var(--sidebar-primary);
|
|
16
|
+
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
17
|
+
--color-sidebar: var(--sidebar);
|
|
18
|
+
--color-chart-5: var(--chart-5);
|
|
19
|
+
--color-chart-4: var(--chart-4);
|
|
20
|
+
--color-chart-3: var(--chart-3);
|
|
21
|
+
--color-chart-2: var(--chart-2);
|
|
22
|
+
--color-chart-1: var(--chart-1);
|
|
23
|
+
--color-ring: var(--ring);
|
|
24
|
+
--color-input: var(--input);
|
|
25
|
+
--color-border: var(--border);
|
|
26
|
+
--color-destructive: var(--destructive);
|
|
27
|
+
--color-accent-foreground: var(--accent-foreground);
|
|
28
|
+
--color-accent: var(--accent);
|
|
29
|
+
--color-muted-foreground: var(--muted-foreground);
|
|
30
|
+
--color-muted: var(--muted);
|
|
31
|
+
--color-secondary-foreground: var(--secondary-foreground);
|
|
32
|
+
--color-secondary: var(--secondary);
|
|
33
|
+
--color-primary-foreground: var(--primary-foreground);
|
|
34
|
+
--color-primary: var(--primary);
|
|
35
|
+
--color-popover-foreground: var(--popover-foreground);
|
|
36
|
+
--color-popover: var(--popover);
|
|
37
|
+
--color-card-foreground: var(--card-foreground);
|
|
38
|
+
--color-card: var(--card);
|
|
39
|
+
--radius-sm: calc(var(--radius) - 4px);
|
|
40
|
+
--radius-md: calc(var(--radius) - 2px);
|
|
41
|
+
--radius-lg: var(--radius);
|
|
42
|
+
--radius-xl: calc(var(--radius) + 4px);
|
|
43
|
+
}
|
|
44
|
+
|
|
3
45
|
:root {
|
|
4
|
-
--
|
|
5
|
-
--
|
|
6
|
-
--
|
|
46
|
+
--background: oklch(1 0 0);
|
|
47
|
+
--foreground: oklch(0.141 0.005 285.823);
|
|
48
|
+
--card: oklch(1 0 0);
|
|
49
|
+
--card-foreground: oklch(0.141 0.005 285.823);
|
|
50
|
+
--popover: oklch(1 0 0);
|
|
51
|
+
--popover-foreground: oklch(0.141 0.005 285.823);
|
|
52
|
+
--primary: oklch(0.21 0.006 285.885);
|
|
53
|
+
--primary-foreground: oklch(0.985 0 0);
|
|
54
|
+
--secondary: oklch(0.967 0.001 286.375);
|
|
55
|
+
--secondary-foreground: oklch(0.21 0.006 285.885);
|
|
56
|
+
--muted: oklch(0.967 0.001 286.375);
|
|
57
|
+
--muted-foreground: oklch(0.552 0.016 285.938);
|
|
58
|
+
--accent: oklch(0.967 0.001 286.375);
|
|
59
|
+
--accent-foreground: oklch(0.21 0.006 285.885);
|
|
60
|
+
--destructive: oklch(0.577 0.245 27.325);
|
|
61
|
+
--border: oklch(0.92 0.004 286.32);
|
|
62
|
+
--input: oklch(0.92 0.004 286.32);
|
|
63
|
+
--ring: oklch(0.705 0.015 286.067);
|
|
64
|
+
--chart-1: oklch(0.646 0.222 41.116);
|
|
65
|
+
--chart-2: oklch(0.6 0.118 184.704);
|
|
66
|
+
--chart-3: oklch(0.398 0.07 227.392);
|
|
67
|
+
--chart-4: oklch(0.828 0.189 84.429);
|
|
68
|
+
--chart-5: oklch(0.769 0.188 70.08);
|
|
69
|
+
--radius: 0.625rem;
|
|
70
|
+
--sidebar: oklch(0.985 0 0);
|
|
71
|
+
--sidebar-foreground: oklch(0.141 0.005 285.823);
|
|
72
|
+
--sidebar-primary: oklch(0.21 0.006 285.885);
|
|
73
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
74
|
+
--sidebar-accent: oklch(0.967 0.001 286.375);
|
|
75
|
+
--sidebar-accent-foreground: oklch(0.21 0.006 285.885);
|
|
76
|
+
--sidebar-border: oklch(0.92 0.004 286.32);
|
|
77
|
+
--sidebar-ring: oklch(0.705 0.015 286.067);
|
|
7
78
|
}
|
|
8
79
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
80
|
+
.dark {
|
|
81
|
+
--background: oklch(0.141 0.005 285.823);
|
|
82
|
+
--foreground: oklch(0.985 0 0);
|
|
83
|
+
--card: oklch(0.141 0.005 285.823);
|
|
84
|
+
--card-foreground: oklch(0.985 0 0);
|
|
85
|
+
--popover: oklch(0.141 0.005 285.823);
|
|
86
|
+
--popover-foreground: oklch(0.985 0 0);
|
|
87
|
+
--primary: oklch(0.985 0 0);
|
|
88
|
+
--primary-foreground: oklch(0.21 0.006 285.885);
|
|
89
|
+
--secondary: oklch(0.274 0.006 286.033);
|
|
90
|
+
--secondary-foreground: oklch(0.985 0 0);
|
|
91
|
+
--muted: oklch(0.274 0.006 286.033);
|
|
92
|
+
--muted-foreground: oklch(0.705 0.015 286.067);
|
|
93
|
+
--accent: oklch(0.274 0.006 286.033);
|
|
94
|
+
--accent-foreground: oklch(0.985 0 0);
|
|
95
|
+
--destructive: oklch(0.396 0.141 25.723);
|
|
96
|
+
--border: oklch(0.274 0.006 286.033);
|
|
97
|
+
--input: oklch(0.274 0.006 286.033);
|
|
98
|
+
--ring: oklch(0.446 0.043 257.431);
|
|
99
|
+
--chart-1: oklch(0.488 0.243 264.376);
|
|
100
|
+
--chart-2: oklch(0.696 0.17 162.48);
|
|
101
|
+
--chart-3: oklch(0.769 0.188 70.08);
|
|
102
|
+
--chart-4: oklch(0.627 0.265 303.9);
|
|
103
|
+
--chart-5: oklch(0.645 0.246 16.439);
|
|
104
|
+
--sidebar: oklch(0.21 0.006 285.885);
|
|
105
|
+
--sidebar-foreground: oklch(0.985 0 0);
|
|
106
|
+
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
107
|
+
--sidebar-primary-foreground: oklch(0.985 0 0);
|
|
108
|
+
--sidebar-accent: oklch(0.274 0.006 286.033);
|
|
109
|
+
--sidebar-accent-foreground: oklch(0.985 0 0);
|
|
110
|
+
--sidebar-border: oklch(0.274 0.006 286.033);
|
|
111
|
+
--sidebar-ring: oklch(0.446 0.043 257.431);
|
|
17
112
|
}
|
|
18
113
|
|
|
19
|
-
@layer
|
|
20
|
-
|
|
21
|
-
|
|
114
|
+
@layer base {
|
|
115
|
+
* {
|
|
116
|
+
@apply border-border outline-ring/50;
|
|
117
|
+
}
|
|
118
|
+
body {
|
|
119
|
+
@apply bg-background text-foreground;
|
|
22
120
|
}
|
|
23
121
|
}
|
|
@@ -5,7 +5,7 @@ import { usePathname } from "next/navigation";
|
|
|
5
5
|
import { resources } from "@/config/resources";
|
|
6
6
|
import * as Icons from "lucide-react";
|
|
7
7
|
import { LogOut } from "lucide-react";
|
|
8
|
-
import {
|
|
8
|
+
import { getBrowserClient } from "@/lib/supabase/client";
|
|
9
9
|
import { useRouter } from "next/navigation";
|
|
10
10
|
|
|
11
11
|
export default function Sidebar() {
|
|
@@ -13,10 +13,7 @@ export default function Sidebar() {
|
|
|
13
13
|
const router = useRouter();
|
|
14
14
|
|
|
15
15
|
const handleLogout = async () => {
|
|
16
|
-
const supabase =
|
|
17
|
-
process.env.NEXT_PUBLIC_SUPABASE_URL!,
|
|
18
|
-
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
|
|
19
|
-
);
|
|
16
|
+
const supabase = getBrowserClient();
|
|
20
17
|
await supabase.auth.signOut();
|
|
21
18
|
router.push("/login");
|
|
22
19
|
router.refresh();
|
|
@@ -0,0 +1,22 @@
|
|
|
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": "app/globals.css",
|
|
9
|
+
"baseColor": "zinc",
|
|
10
|
+
"cssVariables": true,
|
|
11
|
+
"prefix": ""
|
|
12
|
+
},
|
|
13
|
+
"iconLibrary": "lucide",
|
|
14
|
+
"aliases": {
|
|
15
|
+
"components": "@/components",
|
|
16
|
+
"utils": "@/lib/utils",
|
|
17
|
+
"ui": "@/components/ui",
|
|
18
|
+
"lib": "@/lib",
|
|
19
|
+
"hooks": "@/hooks"
|
|
20
|
+
},
|
|
21
|
+
"registries": {}
|
|
22
|
+
}
|
|
@@ -56,6 +56,9 @@ export function useResource(config: ResourceConfig) {
|
|
|
56
56
|
};
|
|
57
57
|
|
|
58
58
|
const remove = async (id: string) => {
|
|
59
|
+
if (!window.confirm("Are you sure you want to delete this record?")) {
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
59
62
|
setIsDeleting(true);
|
|
60
63
|
try {
|
|
61
64
|
const result = await deleteResource(config.table, id);
|
|
@@ -2,11 +2,10 @@ import { BaseService } from './base.service';
|
|
|
2
2
|
import { getServerClient } from "@/lib/supabase/server";
|
|
3
3
|
|
|
4
4
|
export class ResourceService extends BaseService {
|
|
5
|
-
constructor(table: string) {
|
|
5
|
+
constructor(table: string = '') {
|
|
6
6
|
super(table);
|
|
7
7
|
}
|
|
8
8
|
|
|
9
|
-
// Fetch options for relation fields (e.g. categories for products)
|
|
10
9
|
async getRelationOptions(table: string, displayField: string) {
|
|
11
10
|
const supabase = await getServerClient();
|
|
12
11
|
const { data, error } = await supabase
|
|
@@ -19,8 +18,4 @@ export class ResourceService extends BaseService {
|
|
|
19
18
|
}
|
|
20
19
|
}
|
|
21
20
|
|
|
22
|
-
export const resourceService = new ResourceService(
|
|
23
|
-
|
|
24
|
-
// Improved design: We don't export a single instance but rather helper functions or the class itself.
|
|
25
|
-
// But for FormLayout client-side fetching, we need a server action or API route.
|
|
26
|
-
// Let's create a server action for this instead.
|
|
21
|
+
export const resourceService = new ResourceService();
|
|
@@ -1,9 +1,13 @@
|
|
|
1
1
|
import { createBrowserClient } from "@supabase/ssr";
|
|
2
2
|
|
|
3
3
|
export function getBrowserClient() {
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
)
|
|
4
|
+
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
|
5
|
+
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;
|
|
6
|
+
|
|
7
|
+
if (!supabaseUrl || !supabaseKey) {
|
|
8
|
+
throw new Error('Missing Supabase credentials. Please check NEXT_PUBLIC_SUPABASE_URL and NEXT_PUBLIC_SUPABASE_ANON_KEY in .env');
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
return createBrowserClient(supabaseUrl, supabaseKey);
|
|
8
12
|
}
|
|
9
13
|
|
|
@@ -2,9 +2,7 @@ import { type NextRequest } from "next/server";
|
|
|
2
2
|
import { updateSession } from "@/lib/supabase/middleware";
|
|
3
3
|
|
|
4
4
|
export async function middleware(request: NextRequest) {
|
|
5
|
-
|
|
6
|
-
console.log("URL:", process.env.NEXT_PUBLIC_SUPABASE_URL ? "Defined" : "Undefined");
|
|
7
|
-
console.log("Anon Key:", process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY ? "Defined" : "Undefined");
|
|
5
|
+
|
|
8
6
|
return await updateSession(request);
|
|
9
7
|
}
|
|
10
8
|
|
|
@@ -1,9 +1,17 @@
|
|
|
1
1
|
import type { NextConfig } from "next";
|
|
2
2
|
|
|
3
3
|
const nextConfig: NextConfig = {
|
|
4
|
-
/* config options here */
|
|
5
4
|
images: {
|
|
6
|
-
|
|
5
|
+
remotePatterns: [
|
|
6
|
+
{
|
|
7
|
+
protocol: "https",
|
|
8
|
+
hostname: "res.cloudinary.com",
|
|
9
|
+
},
|
|
10
|
+
{
|
|
11
|
+
protocol: "https",
|
|
12
|
+
hostname: "picsum.photos",
|
|
13
|
+
},
|
|
14
|
+
],
|
|
7
15
|
},
|
|
8
16
|
};
|
|
9
17
|
|
|
@@ -11,12 +11,14 @@
|
|
|
11
11
|
"@supabase/ssr": "^0.8.0",
|
|
12
12
|
"@supabase/supabase-js": "^2.94.1",
|
|
13
13
|
"cloudinary": "^2.9.0",
|
|
14
|
+
"clsx": "^2.1.1",
|
|
14
15
|
"lucide-react": "^0.563.0",
|
|
15
16
|
"next": "16.1.6",
|
|
16
17
|
"react": "19.2.3",
|
|
17
18
|
"react-dom": "19.2.3",
|
|
18
19
|
"react-hook-form": "^7.71.1",
|
|
19
|
-
"react-toastify": "^11.0.3"
|
|
20
|
+
"react-toastify": "^11.0.3",
|
|
21
|
+
"tailwind-merge": "^3.5.0"
|
|
20
22
|
},
|
|
21
23
|
"devDependencies": {
|
|
22
24
|
"@tailwindcss/postcss": "^4",
|
|
@@ -6228,6 +6230,16 @@
|
|
|
6228
6230
|
"url": "https://github.com/sponsors/ljharb"
|
|
6229
6231
|
}
|
|
6230
6232
|
},
|
|
6233
|
+
"node_modules/tailwind-merge": {
|
|
6234
|
+
"version": "3.5.0",
|
|
6235
|
+
"resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.5.0.tgz",
|
|
6236
|
+
"integrity": "sha512-I8K9wewnVDkL1NTGoqWmVEIlUcB9gFriAEkXkfCjX5ib8ezGxtR3xD7iZIxrfArjEsH7F1CHD4RFUtxefdqV/A==",
|
|
6237
|
+
"license": "MIT",
|
|
6238
|
+
"funding": {
|
|
6239
|
+
"type": "github",
|
|
6240
|
+
"url": "https://github.com/sponsors/dcastil"
|
|
6241
|
+
}
|
|
6242
|
+
},
|
|
6231
6243
|
"node_modules/tailwindcss": {
|
|
6232
6244
|
"version": "4.1.18",
|
|
6233
6245
|
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz",
|
|
@@ -12,12 +12,14 @@
|
|
|
12
12
|
"@supabase/ssr": "^0.8.0",
|
|
13
13
|
"@supabase/supabase-js": "^2.94.1",
|
|
14
14
|
"cloudinary": "^2.9.0",
|
|
15
|
+
"clsx": "^2.1.1",
|
|
15
16
|
"lucide-react": "^0.563.0",
|
|
16
17
|
"next": "16.1.6",
|
|
17
18
|
"react": "19.2.3",
|
|
18
19
|
"react-dom": "19.2.3",
|
|
19
20
|
"react-hook-form": "^7.71.1",
|
|
20
|
-
"react-toastify": "^11.0.3"
|
|
21
|
+
"react-toastify": "^11.0.3",
|
|
22
|
+
"tailwind-merge": "^3.5.0"
|
|
21
23
|
},
|
|
22
24
|
"devDependencies": {
|
|
23
25
|
"@tailwindcss/postcss": "^4",
|
|
@@ -18,4 +18,8 @@ CLOUDINARY_API_KEY=your_cloudinary_api_key_here
|
|
|
18
18
|
CLOUDINARY_API_SECRET=your_cloudinary_api_secret_here
|
|
19
19
|
CLOUDINARY_ID=your_cloud_name_here
|
|
20
20
|
NEXT_PUBLIC_CLOUDINARY_CLOUD_NAME=your_cloud_name_here
|
|
21
|
-
CLOUDINARY_URL=cloudinary://your_api_key:your_api_secret@your_cloud_name
|
|
21
|
+
CLOUDINARY_URL=cloudinary://your_api_key:your_api_secret@your_cloud_name
|
|
22
|
+
|
|
23
|
+
# Site Configuration
|
|
24
|
+
NEXT_PUBLIC_SITE_URL=http://localhost:3000
|
|
25
|
+
NEXT_PUBLIC_GA_ID=your_google_analytics_id_here
|
|
@@ -15,7 +15,6 @@
|
|
|
15
15
|
"@supabase/supabase-js": "^2.94.1",
|
|
16
16
|
"@vercel/analytics": "^1.6.1",
|
|
17
17
|
"cloudinary": "^2.9.0",
|
|
18
|
-
"dotenv": "^17.2.4",
|
|
19
18
|
"framer-motion": "^12.23.26",
|
|
20
19
|
"lucide-react": "^0.562.0",
|
|
21
20
|
"next": "^16.1.6",
|
|
@@ -26,9 +25,6 @@
|
|
|
26
25
|
"react-icons": "^5.5.0",
|
|
27
26
|
"react-redux": "^9.2.0",
|
|
28
27
|
"react-toastify": "^11.0.5",
|
|
29
|
-
"redux": "^5.0.1",
|
|
30
|
-
"redux-logger": "^3.0.6",
|
|
31
|
-
"redux-thunk": "^3.1.0",
|
|
32
28
|
"resend": "^6.6.0",
|
|
33
29
|
"zod": "^4.3.5"
|
|
34
30
|
},
|
|
@@ -2814,13 +2810,6 @@
|
|
|
2814
2810
|
}
|
|
2815
2811
|
}
|
|
2816
2812
|
},
|
|
2817
|
-
"node_modules/deep-diff": {
|
|
2818
|
-
"version": "0.3.8",
|
|
2819
|
-
"resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-0.3.8.tgz",
|
|
2820
|
-
"integrity": "sha512-yVn6RZmHiGnxRKR9sJb3iVV2XTF1Ghh2DiWRZ3dMnGc43yUdWWF/kX6lQyk3+P84iprfWKU/8zFTrlkvtFm1ug==",
|
|
2821
|
-
"deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
|
|
2822
|
-
"license": "MIT"
|
|
2823
|
-
},
|
|
2824
2813
|
"node_modules/deep-is": {
|
|
2825
2814
|
"version": "0.1.4",
|
|
2826
2815
|
"resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
|
|
@@ -2887,18 +2876,6 @@
|
|
|
2887
2876
|
"node": ">=0.10.0"
|
|
2888
2877
|
}
|
|
2889
2878
|
},
|
|
2890
|
-
"node_modules/dotenv": {
|
|
2891
|
-
"version": "17.2.4",
|
|
2892
|
-
"resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.4.tgz",
|
|
2893
|
-
"integrity": "sha512-mudtfb4zRB4bVvdj0xRo+e6duH1csJRM8IukBqfTRvHotn9+LBXB8ynAidP9zHqoRC/fsllXgk4kCKlR21fIhw==",
|
|
2894
|
-
"license": "BSD-2-Clause",
|
|
2895
|
-
"engines": {
|
|
2896
|
-
"node": ">=12"
|
|
2897
|
-
},
|
|
2898
|
-
"funding": {
|
|
2899
|
-
"url": "https://dotenvx.com"
|
|
2900
|
-
}
|
|
2901
|
-
},
|
|
2902
2879
|
"node_modules/dunder-proto": {
|
|
2903
2880
|
"version": "1.0.1",
|
|
2904
2881
|
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
|
|
@@ -5622,15 +5599,6 @@
|
|
|
5622
5599
|
"integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
|
|
5623
5600
|
"license": "MIT"
|
|
5624
5601
|
},
|
|
5625
|
-
"node_modules/redux-logger": {
|
|
5626
|
-
"version": "3.0.6",
|
|
5627
|
-
"resolved": "https://registry.npmjs.org/redux-logger/-/redux-logger-3.0.6.tgz",
|
|
5628
|
-
"integrity": "sha512-JoCIok7bg/XpqA1JqCqXFypuqBbQzGQySrhFzewB7ThcnysTO30l4VCst86AuB9T9tuT03MAA56Jw2PNhRSNCg==",
|
|
5629
|
-
"license": "MIT",
|
|
5630
|
-
"dependencies": {
|
|
5631
|
-
"deep-diff": "^0.3.5"
|
|
5632
|
-
}
|
|
5633
|
-
},
|
|
5634
5602
|
"node_modules/redux-thunk": {
|
|
5635
5603
|
"version": "3.1.0",
|
|
5636
5604
|
"resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
|
|
@@ -12,6 +12,7 @@
|
|
|
12
12
|
"dependencies": {
|
|
13
13
|
"@hookform/resolvers": "^5.2.2",
|
|
14
14
|
"@next/third-parties": "^16.1.1",
|
|
15
|
+
"@reduxjs/toolkit": "^2.11.2",
|
|
15
16
|
"@supabase/ssr": "^0.8.0",
|
|
16
17
|
"@supabase/supabase-js": "^2.94.1",
|
|
17
18
|
"@vercel/analytics": "^1.6.1",
|
|
@@ -24,6 +25,7 @@
|
|
|
24
25
|
"react-dom": "^19.2.4",
|
|
25
26
|
"react-hook-form": "^7.70.0",
|
|
26
27
|
"react-icons": "^5.5.0",
|
|
28
|
+
"react-redux": "^9.2.0",
|
|
27
29
|
"react-toastify": "^11.0.5",
|
|
28
30
|
"resend": "^6.6.0",
|
|
29
31
|
"zod": "^4.3.5"
|
|
@@ -1,95 +1,54 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
2
|
import { revalidateTag, revalidatePath } from 'next/cache';
|
|
3
|
-
import { categoryService } from '@/lib/services/categoryService';
|
|
4
|
-
import { productService } from '@/lib/services/productService';
|
|
5
|
-
import { clientService } from '@/lib/services/clientService';
|
|
6
|
-
import { projectService } from '@/lib/services/projectService';
|
|
7
3
|
|
|
8
|
-
/**
|
|
9
|
-
* On-demand Revalidation API
|
|
10
|
-
*
|
|
11
|
-
* Usage:
|
|
12
|
-
* POST /api/revalidate
|
|
13
|
-
* Body: { secret: "your-secret", tag?: "categories|products|clients|projects", path?: "/some-path" }
|
|
14
|
-
*
|
|
15
|
-
* Example:
|
|
16
|
-
* curl -X POST http://localhost:3000/api/revalidate \
|
|
17
|
-
* -H "Content-Type: application/json" \
|
|
18
|
-
* -d '{"secret":"your-secret","tag":"products"}'
|
|
19
|
-
*/
|
|
20
4
|
export async function POST(request: NextRequest) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
revalidated.push(`path:${path}`);
|
|
5
|
+
try {
|
|
6
|
+
const body = await request.json();
|
|
7
|
+
const { secret, tag, path } = body;
|
|
8
|
+
|
|
9
|
+
if (!secret || secret !== process.env.REVALIDATION_SECRET) {
|
|
10
|
+
return NextResponse.json(
|
|
11
|
+
{ error: 'Invalid or missing secret' },
|
|
12
|
+
{ status: 401 }
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const revalidated: string[] = [];
|
|
17
|
+
|
|
18
|
+
if (tag) {
|
|
19
|
+
revalidateTag(tag, 'everything');
|
|
20
|
+
revalidated.push(`tag:${tag}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (path) {
|
|
24
|
+
revalidatePath(path);
|
|
25
|
+
revalidated.push(`path:${path}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (revalidated.length === 0) {
|
|
29
|
+
return NextResponse.json(
|
|
30
|
+
{ error: 'No tag or path specified' },
|
|
31
|
+
{ status: 400 }
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return NextResponse.json({
|
|
36
|
+
revalidated: true,
|
|
37
|
+
items: revalidated,
|
|
38
|
+
timestamp: new Date().toISOString(),
|
|
39
|
+
});
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error('[Revalidation] Error:', error);
|
|
42
|
+
return NextResponse.json(
|
|
43
|
+
{ error: 'Error revalidating', message: (error as Error).message },
|
|
44
|
+
{ status: 500 }
|
|
45
|
+
);
|
|
63
46
|
}
|
|
64
|
-
|
|
65
|
-
if (revalidated.length === 0) {
|
|
66
|
-
return NextResponse.json(
|
|
67
|
-
{ error: 'No tag or path specified' },
|
|
68
|
-
{ status: 400 }
|
|
69
|
-
);
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
return NextResponse.json({
|
|
73
|
-
revalidated: true,
|
|
74
|
-
items: revalidated,
|
|
75
|
-
timestamp: new Date().toISOString(),
|
|
76
|
-
});
|
|
77
|
-
} catch (error) {
|
|
78
|
-
console.error('[Revalidation] Error:', error);
|
|
79
|
-
return NextResponse.json(
|
|
80
|
-
{
|
|
81
|
-
error: 'Error revalidating',
|
|
82
|
-
message: (error as Error).message,
|
|
83
|
-
},
|
|
84
|
-
{ status: 500 }
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
47
|
}
|
|
88
48
|
|
|
89
|
-
// Optional: GET endpoint to check if the API is working
|
|
90
49
|
export async function GET() {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
50
|
+
return NextResponse.json({
|
|
51
|
+
message: 'Revalidation API is running',
|
|
52
|
+
usage: 'POST with { secret, tag?, path? }',
|
|
53
|
+
});
|
|
54
|
+
}
|
|
@@ -50,19 +50,7 @@
|
|
|
50
50
|
--radius-3xl: calc(var(--radius) + 12px);
|
|
51
51
|
--radius-4xl: calc(var(--radius) + 16px);
|
|
52
52
|
|
|
53
|
-
/* Z-Index
|
|
54
|
-
--z-base: 1;
|
|
55
|
-
--z-dropdown: 1000;
|
|
56
|
-
--z-sticky: 1020;
|
|
57
|
-
--z-fixed: 1030;
|
|
58
|
-
--z-modal-backdrop: 1040;
|
|
59
|
-
--z-modal: 1050;
|
|
60
|
-
--z-popover: 1060;
|
|
61
|
-
--z-tooltip: 1070;
|
|
62
|
-
--z-lightbox: 9999;
|
|
63
|
-
--z-lightbox-controls: 10000;
|
|
64
|
-
|
|
65
|
-
/* Z-Index Utilities */
|
|
53
|
+
/* Z-Index Scale */
|
|
66
54
|
--z-base: 1;
|
|
67
55
|
--z-dropdown: 1000;
|
|
68
56
|
--z-sticky: 1020;
|