create-nextjs-stack 0.1.1 → 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 +49 -18
  21. package/templates/web/package.json +1 -2
  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
@@ -10,6 +10,7 @@
10
10
  "dependencies": {
11
11
  "@hookform/resolvers": "^5.2.2",
12
12
  "@next/third-parties": "^16.1.1",
13
+ "@reduxjs/toolkit": "^2.11.2",
13
14
  "@supabase/ssr": "^0.8.0",
14
15
  "@supabase/supabase-js": "^2.94.1",
15
16
  "@vercel/analytics": "^1.6.1",
@@ -24,8 +25,6 @@
24
25
  "react-icons": "^5.5.0",
25
26
  "react-redux": "^9.2.0",
26
27
  "react-toastify": "^11.0.5",
27
- "redux-logger": "^3.0.6",
28
- "redux-thunk": "^3.1.0",
29
28
  "resend": "^6.6.0",
30
29
  "zod": "^4.3.5"
31
30
  },
@@ -1099,6 +1098,32 @@
1099
1098
  "node": ">=12.4.0"
1100
1099
  }
1101
1100
  },
1101
+ "node_modules/@reduxjs/toolkit": {
1102
+ "version": "2.11.2",
1103
+ "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz",
1104
+ "integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==",
1105
+ "license": "MIT",
1106
+ "dependencies": {
1107
+ "@standard-schema/spec": "^1.0.0",
1108
+ "@standard-schema/utils": "^0.3.0",
1109
+ "immer": "^11.0.0",
1110
+ "redux": "^5.0.1",
1111
+ "redux-thunk": "^3.1.0",
1112
+ "reselect": "^5.1.0"
1113
+ },
1114
+ "peerDependencies": {
1115
+ "react": "^16.9.0 || ^17.0.0 || ^18 || ^19",
1116
+ "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0"
1117
+ },
1118
+ "peerDependenciesMeta": {
1119
+ "react": {
1120
+ "optional": true
1121
+ },
1122
+ "react-redux": {
1123
+ "optional": true
1124
+ }
1125
+ }
1126
+ },
1102
1127
  "node_modules/@rtsao/scc": {
1103
1128
  "version": "1.1.0",
1104
1129
  "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
@@ -1119,6 +1144,12 @@
1119
1144
  "integrity": "sha512-1bnPQqSxSuc3Ii6MhBysoWCg58j97aUjuCSZrGSmDxNqtytIi0k8utUenAwTZN4V5mXXYGsVUI9zeBqy+jBOSQ==",
1120
1145
  "license": "MIT"
1121
1146
  },
1147
+ "node_modules/@standard-schema/spec": {
1148
+ "version": "1.1.0",
1149
+ "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz",
1150
+ "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==",
1151
+ "license": "MIT"
1152
+ },
1122
1153
  "node_modules/@standard-schema/utils": {
1123
1154
  "version": "0.3.0",
1124
1155
  "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz",
@@ -2779,13 +2810,6 @@
2779
2810
  }
2780
2811
  }
2781
2812
  },
2782
- "node_modules/deep-diff": {
2783
- "version": "0.3.8",
2784
- "resolved": "https://registry.npmjs.org/deep-diff/-/deep-diff-0.3.8.tgz",
2785
- "integrity": "sha512-yVn6RZmHiGnxRKR9sJb3iVV2XTF1Ghh2DiWRZ3dMnGc43yUdWWF/kX6lQyk3+P84iprfWKU/8zFTrlkvtFm1ug==",
2786
- "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.",
2787
- "license": "MIT"
2788
- },
2789
2813
  "node_modules/deep-is": {
2790
2814
  "version": "0.1.4",
2791
2815
  "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
@@ -3984,6 +4008,16 @@
3984
4008
  "node": ">= 4"
3985
4009
  }
3986
4010
  },
4011
+ "node_modules/immer": {
4012
+ "version": "11.1.4",
4013
+ "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.4.tgz",
4014
+ "integrity": "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==",
4015
+ "license": "MIT",
4016
+ "funding": {
4017
+ "type": "opencollective",
4018
+ "url": "https://opencollective.com/immer"
4019
+ }
4020
+ },
3987
4021
  "node_modules/import-fresh": {
3988
4022
  "version": "3.3.1",
3989
4023
  "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
@@ -5565,15 +5599,6 @@
5565
5599
  "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==",
5566
5600
  "license": "MIT"
5567
5601
  },
5568
- "node_modules/redux-logger": {
5569
- "version": "3.0.6",
5570
- "resolved": "https://registry.npmjs.org/redux-logger/-/redux-logger-3.0.6.tgz",
5571
- "integrity": "sha512-JoCIok7bg/XpqA1JqCqXFypuqBbQzGQySrhFzewB7ThcnysTO30l4VCst86AuB9T9tuT03MAA56Jw2PNhRSNCg==",
5572
- "license": "MIT",
5573
- "dependencies": {
5574
- "deep-diff": "^0.3.5"
5575
- }
5576
- },
5577
5602
  "node_modules/redux-thunk": {
5578
5603
  "version": "3.1.0",
5579
5604
  "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz",
@@ -5627,6 +5652,12 @@
5627
5652
  "url": "https://github.com/sponsors/ljharb"
5628
5653
  }
5629
5654
  },
5655
+ "node_modules/reselect": {
5656
+ "version": "5.1.1",
5657
+ "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz",
5658
+ "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==",
5659
+ "license": "MIT"
5660
+ },
5630
5661
  "node_modules/resend": {
5631
5662
  "version": "6.9.2",
5632
5663
  "resolved": "https://registry.npmjs.org/resend/-/resend-6.9.2.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",
@@ -26,8 +27,6 @@
26
27
  "react-icons": "^5.5.0",
27
28
  "react-redux": "^9.2.0",
28
29
  "react-toastify": "^11.0.5",
29
- "redux-logger": "^3.0.6",
30
- "redux-thunk": "^3.1.0",
31
30
  "resend": "^6.6.0",
32
31
  "zod": "^4.3.5"
33
32
  },
@@ -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
+ }