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.
- package/README.md +502 -31
- package/package.json +6 -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/app/layout.tsx +4 -1
- 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 +11 -5
- package/templates/admin/src/lib/providers/StoreProvider.tsx +12 -0
- package/templates/admin/src/store/actions/index.ts +2 -0
- package/templates/admin/src/store/hooks.ts +11 -0
- package/templates/admin/src/store/index.ts +17 -0
- package/templates/admin/src/store/reducers/index.ts +11 -0
- package/templates/admin/src/store/types/index.ts +2 -0
- package/templates/admin/tsconfig.json +1 -1
- package/templates/web/.env.example +5 -1
- package/templates/web/package-lock.json +49 -18
- package/templates/web/package.json +3 -4
- 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/hooks.ts +11 -0
- package/templates/web/src/store/index.ts +11 -7
- package/templates/web/src/store/reducers/index.ts +1 -3
- 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
|
}
|
|
@@ -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}>
|
|
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 {
|
|
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",
|
|
@@ -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-
|
|
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,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;
|
|
@@ -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
|
-
"
|
|
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": "
|
|
42
|
+
"eslint-config-next": "^16.1.6",
|
|
44
43
|
"tailwindcss": "^4",
|
|
45
44
|
"tw-animate-css": "^1.4.0",
|
|
46
45
|
"typescript": "^5"
|