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.
Files changed (69) hide show
  1. package/README.md +502 -31
  2. package/package.json +1 -1
  3. package/templates/admin/app/(dashboard)/[resource]/[id]/page.tsx +6 -5
  4. package/templates/admin/app/(dashboard)/[resource]/new/page.tsx +11 -12
  5. package/templates/admin/app/(dashboard)/[resource]/page.tsx +4 -3
  6. package/templates/admin/app/actions/upload.ts +0 -7
  7. package/templates/admin/app/globals.css +112 -14
  8. package/templates/admin/components/admin/Sidebar.tsx +2 -5
  9. package/templates/admin/components.json +22 -0
  10. package/templates/admin/hooks/useResource.ts +3 -0
  11. package/templates/admin/lib/services/resource.service.ts +2 -7
  12. package/templates/admin/lib/supabase/client.ts +8 -4
  13. package/templates/admin/lib/supabase/server.ts +1 -1
  14. package/templates/admin/lib/utils.ts +6 -0
  15. package/templates/admin/middleware.ts +1 -3
  16. package/templates/admin/next.config.ts +10 -2
  17. package/templates/admin/package-lock.json +13 -1
  18. package/templates/admin/package.json +3 -1
  19. package/templates/web/.env.example +5 -1
  20. package/templates/web/package-lock.json +0 -32
  21. package/templates/web/package.json +2 -0
  22. package/templates/web/postcss.config.mjs +3 -1
  23. package/templates/web/src/app/api/revalidate/route.ts +46 -87
  24. package/templates/web/src/app/globals.css +1 -13
  25. package/templates/web/src/app/layout.tsx +4 -46
  26. package/templates/web/src/app/robots.ts +1 -1
  27. package/templates/web/src/app/sitemap.ts +27 -31
  28. package/templates/web/src/lib/seo/metadata.ts +5 -5
  29. package/templates/web/src/lib/seo/seo.config.ts +55 -59
  30. package/templates/web/src/lib/seo/seo.types.ts +1 -7
  31. package/templates/web/src/lib/services/categories.service.ts +3 -3
  32. package/templates/web/src/lib/services/clients.service.ts +2 -2
  33. package/templates/web/src/lib/services/products.service.ts +3 -3
  34. package/templates/web/src/lib/services/projects.service.ts +3 -3
  35. package/templates/web/src/lib/services/users.service.ts +2 -2
  36. package/templates/web/src/lib/supabase/client.ts +1 -1
  37. package/templates/web/src/lib/supabase/server.ts +1 -1
  38. package/templates/web/src/store/index.ts +4 -9
  39. package/templates/admin/app/(dashboard)/categories/[id]/page.tsx +0 -22
  40. package/templates/admin/app/(dashboard)/categories/new/page.tsx +0 -5
  41. package/templates/admin/app/(dashboard)/categories/page.tsx +0 -33
  42. package/templates/admin/app/(dashboard)/clients/[id]/page.tsx +0 -22
  43. package/templates/admin/app/(dashboard)/clients/new/page.tsx +0 -5
  44. package/templates/admin/app/(dashboard)/clients/page.tsx +0 -33
  45. package/templates/admin/app/(dashboard)/products/[id]/page.tsx +0 -22
  46. package/templates/admin/app/(dashboard)/products/new/page.tsx +0 -5
  47. package/templates/admin/app/(dashboard)/products/page.tsx +0 -33
  48. package/templates/admin/app/(dashboard)/projects/[id]/page.tsx +0 -22
  49. package/templates/admin/app/(dashboard)/projects/new/page.tsx +0 -5
  50. package/templates/admin/app/(dashboard)/projects/page.tsx +0 -33
  51. package/templates/admin/app/(dashboard)/users/[id]/page.tsx +0 -22
  52. package/templates/admin/app/(dashboard)/users/new/page.tsx +0 -5
  53. package/templates/admin/app/(dashboard)/users/page.tsx +0 -33
  54. package/templates/admin/components/categories/CategoryForm.tsx +0 -24
  55. package/templates/admin/components/categories/CategoryList.tsx +0 -113
  56. package/templates/admin/components/clients/ClientForm.tsx +0 -24
  57. package/templates/admin/components/clients/ClientList.tsx +0 -113
  58. package/templates/admin/components/products/ProductForm.tsx +0 -24
  59. package/templates/admin/components/products/ProductList.tsx +0 -117
  60. package/templates/admin/components/projects/ProjectForm.tsx +0 -24
  61. package/templates/admin/components/projects/ProjectList.tsx +0 -121
  62. package/templates/admin/components/users/UserForm.tsx +0 -39
  63. package/templates/admin/components/users/UserList.tsx +0 -101
  64. package/templates/web/src/lib/services/categoryService.ts +0 -251
  65. package/templates/web/src/lib/services/clientService.ts +0 -132
  66. package/templates/web/src/lib/services/productService.ts +0 -261
  67. package/templates/web/src/lib/services/projectService.ts +0 -234
  68. package/templates/web/src/lib/utils/cache.ts +0 -98
  69. 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
- --foreground-rgb: 0, 0, 0;
5
- --background-start-rgb: 214, 219, 220;
6
- --background-end-rgb: 255, 255, 255;
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
- body {
10
- color: rgb(var(--foreground-rgb));
11
- background: linear-gradient(
12
- to bottom,
13
- transparent,
14
- rgb(var(--background-end-rgb))
15
- )
16
- rgb(var(--background-start-rgb));
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 utilities {
20
- .text-balance {
21
- text-wrap: balance;
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 { createBrowserClient } from "@supabase/ssr";
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 = createBrowserClient(
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(''); // Placeholder, instantiated per use really, strictly for static methods if needed or we change design.
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
- return createBrowserClient(
5
- process.env.NEXT_PUBLIC_SUPABASE_URL!,
6
- process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
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
 
@@ -6,7 +6,7 @@ export async function getServerClient() {
6
6
 
7
7
  return createServerClient(
8
8
  process.env.NEXT_PUBLIC_SUPABASE_URL!,
9
- process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
9
+ process.env.SUPABASE_SERVICE_ROLE_KEY!,
10
10
  {
11
11
  cookies: {
12
12
  getAll() {
@@ -0,0 +1,6 @@
1
+ import { type ClassValue, clsx } from "clsx"
2
+ import { twMerge } from "tailwind-merge"
3
+
4
+ export function cn(...inputs: ClassValue[]) {
5
+ return twMerge(clsx(inputs))
6
+ }
@@ -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
- console.log("Middleware running");
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
- domains: ["res.cloudinary.com","picsum.photos"],
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,5 +1,7 @@
1
1
  const config = {
2
- plugins: ["@tailwindcss/postcss"],
2
+ plugins: {
3
+ "@tailwindcss/postcss": {},
4
+ },
3
5
  };
4
6
 
5
7
  export default config;
@@ -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
- try {
22
- const body = await request.json();
23
- const { secret, tag, path } = body;
24
-
25
- // Verify secret
26
- if (!secret || secret !== process.env.REVALIDATION_SECRET) {
27
- return NextResponse.json(
28
- { error: 'Invalid or missing secret' },
29
- { status: 401 }
30
- );
31
- }
32
-
33
- const revalidated: string[] = [];
34
-
35
- // Revalidate by tag
36
- if (tag) {
37
- revalidateTag(tag, 'max');
38
- revalidated.push(`tag:${tag}`);
39
-
40
- // Also invalidate memory cache for the corresponding service
41
- switch (tag) {
42
- case 'categories':
43
- categoryService.invalidateCache();
44
- break;
45
- case 'products':
46
- productService.invalidateCache();
47
- break;
48
- case 'clients':
49
- clientService.invalidateCache();
50
- break;
51
- case 'projects':
52
- projectService.invalidateCache();
53
- break;
54
- default:
55
- console.warn(`[Revalidation] Unknown tag: ${tag}`);
56
- }
57
- }
58
-
59
- // Revalidate by path
60
- if (path) {
61
- revalidatePath(path);
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
- return NextResponse.json({
92
- message: 'Revalidation API is running',
93
- usage: 'POST with { secret, tag?, path? }',
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 Hierarchy */
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;