create-nextjs-stack 0.1.1 → 0.1.4

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 (79) hide show
  1. package/README.md +502 -31
  2. package/package.json +6 -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/app/layout.tsx +4 -1
  9. package/templates/admin/components/admin/Sidebar.tsx +2 -5
  10. package/templates/admin/components.json +22 -0
  11. package/templates/admin/hooks/useResource.ts +3 -0
  12. package/templates/admin/lib/services/resource.service.ts +2 -7
  13. package/templates/admin/lib/supabase/client.ts +8 -4
  14. package/templates/admin/lib/supabase/server.ts +1 -1
  15. package/templates/admin/lib/utils.ts +6 -0
  16. package/templates/admin/middleware.ts +1 -3
  17. package/templates/admin/next.config.ts +10 -2
  18. package/templates/admin/package-lock.json +13 -1
  19. package/templates/admin/package.json +11 -5
  20. package/templates/admin/src/lib/providers/StoreProvider.tsx +12 -0
  21. package/templates/admin/src/store/actions/index.ts +2 -0
  22. package/templates/admin/src/store/hooks.ts +11 -0
  23. package/templates/admin/src/store/index.ts +17 -0
  24. package/templates/admin/src/store/reducers/index.ts +11 -0
  25. package/templates/admin/src/store/types/index.ts +2 -0
  26. package/templates/admin/tsconfig.json +1 -1
  27. package/templates/web/.env.example +5 -1
  28. package/templates/web/package-lock.json +49 -18
  29. package/templates/web/package.json +3 -4
  30. package/templates/web/postcss.config.mjs +3 -1
  31. package/templates/web/src/app/api/revalidate/route.ts +46 -87
  32. package/templates/web/src/app/globals.css +1 -13
  33. package/templates/web/src/app/layout.tsx +4 -46
  34. package/templates/web/src/app/robots.ts +1 -1
  35. package/templates/web/src/app/sitemap.ts +27 -31
  36. package/templates/web/src/lib/seo/metadata.ts +5 -5
  37. package/templates/web/src/lib/seo/seo.config.ts +55 -59
  38. package/templates/web/src/lib/seo/seo.types.ts +1 -7
  39. package/templates/web/src/lib/services/categories.service.ts +3 -3
  40. package/templates/web/src/lib/services/clients.service.ts +2 -2
  41. package/templates/web/src/lib/services/products.service.ts +3 -3
  42. package/templates/web/src/lib/services/projects.service.ts +3 -3
  43. package/templates/web/src/lib/services/users.service.ts +2 -2
  44. package/templates/web/src/lib/supabase/client.ts +1 -1
  45. package/templates/web/src/lib/supabase/server.ts +1 -1
  46. package/templates/web/src/store/hooks.ts +11 -0
  47. package/templates/web/src/store/index.ts +11 -7
  48. package/templates/web/src/store/reducers/index.ts +1 -3
  49. package/templates/admin/app/(dashboard)/categories/[id]/page.tsx +0 -22
  50. package/templates/admin/app/(dashboard)/categories/new/page.tsx +0 -5
  51. package/templates/admin/app/(dashboard)/categories/page.tsx +0 -33
  52. package/templates/admin/app/(dashboard)/clients/[id]/page.tsx +0 -22
  53. package/templates/admin/app/(dashboard)/clients/new/page.tsx +0 -5
  54. package/templates/admin/app/(dashboard)/clients/page.tsx +0 -33
  55. package/templates/admin/app/(dashboard)/products/[id]/page.tsx +0 -22
  56. package/templates/admin/app/(dashboard)/products/new/page.tsx +0 -5
  57. package/templates/admin/app/(dashboard)/products/page.tsx +0 -33
  58. package/templates/admin/app/(dashboard)/projects/[id]/page.tsx +0 -22
  59. package/templates/admin/app/(dashboard)/projects/new/page.tsx +0 -5
  60. package/templates/admin/app/(dashboard)/projects/page.tsx +0 -33
  61. package/templates/admin/app/(dashboard)/users/[id]/page.tsx +0 -22
  62. package/templates/admin/app/(dashboard)/users/new/page.tsx +0 -5
  63. package/templates/admin/app/(dashboard)/users/page.tsx +0 -33
  64. package/templates/admin/components/categories/CategoryForm.tsx +0 -24
  65. package/templates/admin/components/categories/CategoryList.tsx +0 -113
  66. package/templates/admin/components/clients/ClientForm.tsx +0 -24
  67. package/templates/admin/components/clients/ClientList.tsx +0 -113
  68. package/templates/admin/components/products/ProductForm.tsx +0 -24
  69. package/templates/admin/components/products/ProductList.tsx +0 -117
  70. package/templates/admin/components/projects/ProjectForm.tsx +0 -24
  71. package/templates/admin/components/projects/ProjectList.tsx +0 -121
  72. package/templates/admin/components/users/UserForm.tsx +0 -39
  73. package/templates/admin/components/users/UserList.tsx +0 -101
  74. package/templates/web/src/lib/services/categoryService.ts +0 -251
  75. package/templates/web/src/lib/services/clientService.ts +0 -132
  76. package/templates/web/src/lib/services/productService.ts +0 -261
  77. package/templates/web/src/lib/services/projectService.ts +0 -234
  78. package/templates/web/src/lib/utils/cache.ts +0 -98
  79. 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
  }
@@ -2,6 +2,7 @@ import type { Metadata } from "next";
2
2
  import { Inter } from "next/font/google";
3
3
  import "./globals.css";
4
4
  import "react-toastify/dist/ReactToastify.css";
5
+ import StoreProvider from "@/lib/providers/StoreProvider";
5
6
 
6
7
  const inter = Inter({ subsets: ["latin"] });
7
8
 
@@ -17,7 +18,9 @@ export default function RootLayout({
17
18
  }>) {
18
19
  return (
19
20
  <html lang="en">
20
- <body className={inter.className}>{children}</body>
21
+ <body className={inter.className}>
22
+ <StoreProvider>{children}</StoreProvider>
23
+ </body>
21
24
  </html>
22
25
  );
23
26
  }
@@ -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",
@@ -9,24 +9,30 @@
9
9
  "lint": "eslint"
10
10
  },
11
11
  "dependencies": {
12
+ "@reduxjs/toolkit": "^2.11.2",
12
13
  "@supabase/ssr": "^0.8.0",
13
14
  "@supabase/supabase-js": "^2.94.1",
14
15
  "cloudinary": "^2.9.0",
16
+ "clsx": "^2.1.1",
15
17
  "lucide-react": "^0.563.0",
16
- "next": "16.1.6",
17
- "react": "19.2.3",
18
- "react-dom": "19.2.3",
18
+ "next": "^16.1.6",
19
+ "react": "^19.2.3",
20
+ "react-dom": "^19.2.3",
19
21
  "react-hook-form": "^7.71.1",
20
- "react-toastify": "^11.0.3"
22
+ "react-redux": "^9.2.0",
23
+ "react-toastify": "^11.0.3",
24
+ "redux-logger": "^3.0.6",
25
+ "tailwind-merge": "^3.5.0"
21
26
  },
22
27
  "devDependencies": {
23
28
  "@tailwindcss/postcss": "^4",
24
29
  "@types/node": "^20",
25
30
  "@types/react": "^19",
26
31
  "@types/react-dom": "^19",
32
+ "@types/redux-logger": "^3.0.13",
27
33
  "babel-plugin-react-compiler": "1.0.0",
28
34
  "eslint": "^9",
29
- "eslint-config-next": "16.1.6",
35
+ "eslint-config-next": "^16.1.6",
30
36
  "tailwindcss": "^4",
31
37
  "typescript": "^5"
32
38
  }
@@ -0,0 +1,12 @@
1
+ "use client";
2
+
3
+ import { Provider } from "react-redux";
4
+ import store from "@/store";
5
+
6
+ export default function StoreProvider({
7
+ children,
8
+ }: {
9
+ children: React.ReactNode;
10
+ }) {
11
+ return <Provider store={store}>{children}</Provider>;
12
+ }
@@ -0,0 +1,2 @@
1
+ // Export actions here
2
+ // export * from './exampleActions';
@@ -0,0 +1,11 @@
1
+ import { useDispatch, useSelector } from 'react-redux';
2
+ import type { AppDispatch, RootState } from '.';
3
+
4
+ /**
5
+ * Typed hook wrappers — use these instead of plain useDispatch/useSelector.
6
+ * @example const dispatch = useAppDispatch();
7
+ * @example const value = useAppSelector((state) => state.someSlice.value);
8
+ */
9
+ export const useAppDispatch = () => useDispatch<AppDispatch>();
10
+ export const useAppSelector = <T>(selector: (state: RootState) => T): T =>
11
+ useSelector(selector);
@@ -0,0 +1,17 @@
1
+ import { configureStore } from '@reduxjs/toolkit';
2
+ import logger from 'redux-logger';
3
+ import rootReducer from './reducers';
4
+
5
+ const store = configureStore({
6
+ reducer: rootReducer,
7
+ middleware: (getDefaultMiddleware) =>
8
+ process.env.NODE_ENV !== 'production'
9
+ ? getDefaultMiddleware().concat(logger)
10
+ : getDefaultMiddleware(),
11
+ });
12
+
13
+ // Inferred types — export and use via store/hooks.ts
14
+ export type RootState = ReturnType<typeof store.getState>;
15
+ export type AppDispatch = typeof store.dispatch;
16
+
17
+ export default store;
@@ -0,0 +1,11 @@
1
+ import { combineReducers } from '@reduxjs/toolkit';
2
+ // Import your reducers here
3
+ // import exampleReducer from './exampleReducer';
4
+
5
+ const rootReducer = combineReducers({
6
+ // example: exampleReducer,
7
+ // Add a placeholder to prevent error if no reducers yet
8
+ _placeholder: (state = {}) => state,
9
+ });
10
+
11
+ export default rootReducer;
@@ -0,0 +1,2 @@
1
+ // Define global types for specific actions if needed
2
+ // export const EXAMPLE_ACTION = 'EXAMPLE_ACTION';
@@ -19,7 +19,7 @@
19
19
  }
20
20
  ],
21
21
  "paths": {
22
- "@/*": ["./*"]
22
+ "@/*": ["./src/*"]
23
23
  }
24
24
  },
25
25
  "include": [
@@ -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",
@@ -25,9 +26,8 @@
25
26
  "react-hook-form": "^7.70.0",
26
27
  "react-icons": "^5.5.0",
27
28
  "react-redux": "^9.2.0",
28
- "react-toastify": "^11.0.5",
29
29
  "redux-logger": "^3.0.6",
30
- "redux-thunk": "^3.1.0",
30
+ "react-toastify": "^11.0.5",
31
31
  "resend": "^6.6.0",
32
32
  "zod": "^4.3.5"
33
33
  },
@@ -37,10 +37,9 @@
37
37
  "@types/node": "^20",
38
38
  "@types/react": "^19",
39
39
  "@types/react-dom": "^19",
40
- "@types/react-redux": "^7.1.34",
41
40
  "@types/redux-logger": "^3.0.13",
42
41
  "eslint": "^9",
43
- "eslint-config-next": "15.5.9",
42
+ "eslint-config-next": "^16.1.6",
44
43
  "tailwindcss": "^4",
45
44
  "tw-animate-css": "^1.4.0",
46
45
  "typescript": "^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;