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
@@ -1,113 +0,0 @@
1
- "use client";
2
-
3
- import Link from "next/link";
4
- import Image from "next/image";
5
- import { ResourceConfig } from "@/config/resources";
6
- import { useResource } from "@/hooks/useResource";
7
- import { Pencil, Trash2 } from "lucide-react";
8
-
9
- interface ClientListProps {
10
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
- items: any[];
12
- config: ResourceConfig;
13
- }
14
-
15
- import { useState } from "react";
16
- import DeleteModal from "@/components/admin/DeleteModal";
17
-
18
- export default function ClientList({ items, config }: ClientListProps) {
19
- const { remove, isDeleting } = useResource(config);
20
- const [deleteId, setDeleteId] = useState<string | null>(null);
21
-
22
- const handleDelete = async () => {
23
- if (deleteId) {
24
- const success = await remove(deleteId);
25
- if (success) {
26
- setDeleteId(null);
27
- }
28
- }
29
- };
30
-
31
- return (
32
- <>
33
- <div className="bg-white rounded-lg shadow overflow-hidden">
34
- <div className="overflow-x-auto">
35
- <table className="w-full text-left">
36
- <thead className="bg-gray-50 text-gray-500 uppercase text-xs font-semibold">
37
- <tr>
38
- <th className="px-6 py-3">Logo</th>
39
- <th className="px-6 py-3">Name</th>
40
- <th className="px-6 py-3">Website</th>
41
- <th className="px-6 py-3 text-right">Actions</th>
42
- </tr>
43
- </thead>
44
- <tbody className="divide-y divide-gray-200">
45
- {items?.map((item) => (
46
- <tr key={item.id} className="hover:bg-gray-50 transition">
47
- <td className="px-6 py-4">
48
- <div className="relative w-10 h-10 bg-gray-100 rounded overflow-hidden">
49
- {item.logo_url ? (
50
- <Image
51
- src={item.logo_url}
52
- alt={item.name}
53
- fill
54
- className="object-contain"
55
- />
56
- ) : (
57
- <span className="text-xs text-gray-400 flex items-center justify-center h-full">No Logo</span>
58
- )}
59
- </div>
60
- </td>
61
- <td className="px-6 py-4 text-gray-900 font-medium">{item.name}</td>
62
- <td className="px-6 py-4 text-gray-500">
63
- {item.website ? (
64
- <a href={item.website} target="_blank" rel="noopener noreferrer" className="text-blue-600 hover:underline">
65
- {item.website}
66
- </a>
67
- ) : (
68
- "-"
69
- )}
70
- </td>
71
- <td className="px-6 py-4 text-right">
72
- <div className="flex justify-end gap-2">
73
- <Link
74
- href={`/clients/${item.id}`}
75
- className="flex items-center text-indigo-600 hover:text-indigo-900 bg-indigo-50 hover:bg-indigo-100 px-3 py-1.5 rounded-md transition-colors duration-200"
76
- title="Edit"
77
- >
78
- <Pencil className="w-4 h-4" />
79
- </Link>
80
- <button
81
- onClick={() => setDeleteId(item.id)}
82
- className="flex items-center text-red-600 hover:text-red-900 bg-red-50 hover:bg-red-100 px-3 py-1.5 rounded-md transition-colors duration-200"
83
- title="Delete"
84
- >
85
- <Trash2 className="w-4 h-4" />
86
- </button>
87
- </div>
88
- </td>
89
- </tr>
90
- ))}
91
- {items?.length === 0 && (
92
- <tr>
93
- <td colSpan={4} className="px-6 py-8 text-center text-gray-500">
94
- No clients found.
95
- </td>
96
- </tr>
97
- )}
98
- </tbody>
99
- </table>
100
- </div>
101
- </div>
102
-
103
- <DeleteModal
104
- isOpen={!!deleteId}
105
- onClose={() => setDeleteId(null)}
106
- onConfirm={handleDelete}
107
- isLoading={isDeleting}
108
- title="Delete Client"
109
- description="Are you sure you want to delete this client? This action cannot be undone."
110
- />
111
- </>
112
- );
113
- }
@@ -1,24 +0,0 @@
1
- "use client";
2
-
3
- import ResourceFormClient from "@/components/admin/ResourceFormClient";
4
- import { resources } from "@/config/resources";
5
-
6
- interface ProductFormProps {
7
- mode: "create" | "update";
8
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
9
- initialData?: any;
10
- id?: string;
11
- }
12
-
13
- export default function ProductForm({ mode, initialData, id }: ProductFormProps) {
14
- const config = resources.find((r) => r.name === "products")!;
15
-
16
- return (
17
- <ResourceFormClient
18
- config={config}
19
- mode={mode}
20
- initialData={initialData}
21
- id={id}
22
- />
23
- );
24
- }
@@ -1,117 +0,0 @@
1
- "use client";
2
-
3
- import Link from "next/link";
4
- import Image from "next/image";
5
- import { ResourceConfig } from "@/config/resources";
6
- import { useResource } from "@/hooks/useResource";
7
- import { Pencil, Trash2 } from "lucide-react";
8
-
9
- interface ProductListProps {
10
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
- items: any[];
12
- config: ResourceConfig;
13
- }
14
-
15
- import { useState } from "react";
16
- import DeleteModal from "@/components/admin/DeleteModal";
17
-
18
- export default function ProductList({ items, config }: ProductListProps) {
19
- const { remove, isDeleting } = useResource(config);
20
- const [deleteId, setDeleteId] = useState<string | null>(null);
21
-
22
- const handleDelete = async () => {
23
- if (deleteId) {
24
- const success = await remove(deleteId);
25
- if (success) {
26
- setDeleteId(null);
27
- }
28
- }
29
- };
30
-
31
- return (
32
- <>
33
- <div className="bg-white rounded-lg shadow overflow-hidden">
34
- <div className="overflow-x-auto">
35
- <table className="w-full text-left">
36
- <thead className="bg-gray-50 text-gray-500 uppercase text-xs font-semibold">
37
- <tr>
38
- <th className="px-6 py-3">Image</th>
39
- <th className="px-6 py-3">Title</th>
40
- <th className="px-6 py-3">Slug</th>
41
- <th className="px-6 py-3">Published</th>
42
- <th className="px-6 py-3 text-right">Actions</th>
43
- </tr>
44
- </thead>
45
- <tbody className="divide-y divide-gray-200">
46
- {items?.map((item) => (
47
- <tr key={item.id} className="hover:bg-gray-50 transition">
48
- <td className="px-6 py-4">
49
- <div className="relative w-10 h-10 bg-gray-100 rounded overflow-hidden">
50
- {item.featured_image_url ? (
51
- <Image
52
- src={item.featured_image_url}
53
- alt={item.title}
54
- fill
55
- className="object-cover"
56
- />
57
- ) : (
58
- <span className="text-xs text-gray-400 flex items-center justify-center h-full">No Img</span>
59
- )}
60
- </div>
61
- </td>
62
- <td className="px-6 py-4 text-gray-900 font-medium">{item.title}</td>
63
- <td className="px-6 py-4 text-gray-500">{item.slug}</td>
64
- <td className="px-6 py-4">
65
- <span
66
- className={`px-2 py-1 text-xs rounded-full ${
67
- item.published
68
- ? "bg-green-100 text-green-700"
69
- : "bg-gray-100 text-gray-600"
70
- }`}
71
- >
72
- {item.published ? "Published" : "Draft"}
73
- </span>
74
- </td>
75
- <td className="px-6 py-4 text-right">
76
- <div className="flex justify-end gap-2">
77
- <Link
78
- href={`/products/${item.id}`}
79
- className="flex items-center text-indigo-600 hover:text-indigo-900 bg-indigo-50 hover:bg-indigo-100 px-3 py-1.5 rounded-md transition-colors duration-200"
80
- title="Edit"
81
- >
82
- <Pencil className="w-4 h-4" />
83
- </Link>
84
- <button
85
- onClick={() => setDeleteId(item.id)}
86
- className="flex items-center text-red-600 hover:text-red-900 bg-red-50 hover:bg-red-100 px-3 py-1.5 rounded-md transition-colors duration-200"
87
- title="Delete"
88
- >
89
- <Trash2 className="w-4 h-4" />
90
- </button>
91
- </div>
92
- </td>
93
- </tr>
94
- ))}
95
- {items?.length === 0 && (
96
- <tr>
97
- <td colSpan={5} className="px-6 py-8 text-center text-gray-500">
98
- No products found.
99
- </td>
100
- </tr>
101
- )}
102
- </tbody>
103
- </table>
104
- </div>
105
- </div>
106
-
107
- <DeleteModal
108
- isOpen={!!deleteId}
109
- onClose={() => setDeleteId(null)}
110
- onConfirm={handleDelete}
111
- isLoading={isDeleting}
112
- title="Delete Product"
113
- description="Are you sure you want to delete this product? This action cannot be undone."
114
- />
115
- </>
116
- );
117
- }
@@ -1,24 +0,0 @@
1
- "use client";
2
-
3
- import ResourceFormClient from "@/components/admin/ResourceFormClient";
4
- import { resources } from "@/config/resources";
5
-
6
- interface ProjectFormProps {
7
- mode: "create" | "update";
8
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
9
- initialData?: any;
10
- id?: string;
11
- }
12
-
13
- export default function ProjectForm({ mode, initialData, id }: ProjectFormProps) {
14
- const config = resources.find((r) => r.name === "projects")!;
15
-
16
- return (
17
- <ResourceFormClient
18
- config={config}
19
- mode={mode}
20
- initialData={initialData}
21
- id={id}
22
- />
23
- );
24
- }
@@ -1,121 +0,0 @@
1
- "use client";
2
-
3
- import Link from "next/link";
4
- import Image from "next/image";
5
- import { ResourceConfig } from "@/config/resources";
6
- import { useResource } from "@/hooks/useResource";
7
- import { Pencil, Trash2 } from "lucide-react";
8
-
9
- interface ProjectListProps {
10
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
11
- items: any[];
12
- config: ResourceConfig;
13
- }
14
-
15
- import { useState } from "react";
16
- import DeleteModal from "@/components/admin/DeleteModal";
17
-
18
- export default function ProjectList({ items, config }: ProjectListProps) {
19
- const { remove, isDeleting } = useResource(config);
20
- const [deleteId, setDeleteId] = useState<string | null>(null);
21
-
22
- const handleDelete = async () => {
23
- if (deleteId) {
24
- const success = await remove(deleteId);
25
- if (success) {
26
- setDeleteId(null);
27
- }
28
- }
29
- };
30
-
31
- return (
32
- <>
33
- <div className="bg-white rounded-lg shadow overflow-hidden">
34
- <div className="overflow-x-auto">
35
- <table className="w-full text-left">
36
- <thead className="bg-gray-50 text-gray-500 uppercase text-xs font-semibold">
37
- <tr>
38
- <th className="px-6 py-3">Image</th>
39
- <th className="px-6 py-3">Title</th>
40
- <th className="px-6 py-3">Client</th>
41
- <th className="px-6 py-3">Slug</th>
42
- <th className="px-6 py-3">Published</th>
43
- <th className="px-6 py-3 text-right">Actions</th>
44
- </tr>
45
- </thead>
46
- <tbody className="divide-y divide-gray-200">
47
- {items?.map((item) => (
48
- <tr key={item.id} className="hover:bg-gray-50 transition">
49
- <td className="px-6 py-4">
50
- <div className="relative w-10 h-10 bg-gray-100 rounded overflow-hidden">
51
- {item.featured_image_url ? (
52
- <Image
53
- src={item.featured_image_url}
54
- alt={item.title}
55
- fill
56
- className="object-cover"
57
- />
58
- ) : (
59
- <span className="text-xs text-gray-400 flex items-center justify-center h-full">No Img</span>
60
- )}
61
- </div>
62
- </td>
63
- <td className="px-6 py-4 text-gray-900 font-medium">{item.title}</td>
64
- <td className="px-6 py-4 text-gray-500">
65
- {item.clients?.name || "-"}
66
- </td>
67
- <td className="px-6 py-4 text-gray-500">{item.slug}</td>
68
- <td className="px-6 py-4">
69
- <span
70
- className={`px-2 py-1 text-xs rounded-full ${
71
- item.published
72
- ? "bg-green-100 text-green-700"
73
- : "bg-gray-100 text-gray-600"
74
- }`}
75
- >
76
- {item.published ? "Published" : "Draft"}
77
- </span>
78
- </td>
79
- <td className="px-6 py-4 text-right">
80
- <div className="flex justify-end gap-2">
81
- <Link
82
- href={`/projects/${item.id}`}
83
- className="flex items-center text-indigo-600 hover:text-indigo-900 bg-indigo-50 hover:bg-indigo-100 px-3 py-1.5 rounded-md transition-colors duration-200"
84
- title="Edit"
85
- >
86
- <Pencil className="w-4 h-4" />
87
- </Link>
88
- <button
89
- onClick={() => setDeleteId(item.id)}
90
- className="flex items-center text-red-600 hover:text-red-900 bg-red-50 hover:bg-red-100 px-3 py-1.5 rounded-md transition-colors duration-200"
91
- title="Delete"
92
- >
93
- <Trash2 className="w-4 h-4" />
94
- </button>
95
- </div>
96
- </td>
97
- </tr>
98
- ))}
99
- {items?.length === 0 && (
100
- <tr>
101
- <td colSpan={6} className="px-6 py-8 text-center text-gray-500">
102
- No projects found.
103
- </td>
104
- </tr>
105
- )}
106
- </tbody>
107
- </table>
108
- </div>
109
- </div>
110
-
111
- <DeleteModal
112
- isOpen={!!deleteId}
113
- onClose={() => setDeleteId(null)}
114
- onConfirm={handleDelete}
115
- isLoading={isDeleting}
116
- title="Delete Project"
117
- description="Are you sure you want to delete this project? This action cannot be undone."
118
- />
119
- </>
120
- );
121
- }
@@ -1,39 +0,0 @@
1
- "use client";
2
-
3
- import ResourceFormClient from "@/components/admin/ResourceFormClient";
4
- import { resources } from "@/config/resources";
5
-
6
- interface UserFormProps {
7
- mode: "create" | "update";
8
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
9
- initialData?: any;
10
- id?: string;
11
- }
12
-
13
- export default function UserForm({ mode, initialData, id }: UserFormProps) {
14
- const config = resources.find((r) => r.name === "users")!;
15
-
16
- return (
17
- <div>
18
- {mode === 'create' && (
19
- <div className="bg-yellow-50 border-l-4 border-yellow-400 p-4 mb-6">
20
- <div className="flex">
21
- <div className="ml-3">
22
- <p className="text-sm text-yellow-700">
23
- Note: Creating a user here only creates a profile record.
24
- It does <strong>not</strong> create a login account.
25
- Use the Supabase Auth dashboard to invite users.
26
- </p>
27
- </div>
28
- </div>
29
- </div>
30
- )}
31
- <ResourceFormClient
32
- config={config}
33
- mode={mode}
34
- initialData={initialData}
35
- id={id}
36
- />
37
- </div>
38
- );
39
- }
@@ -1,101 +0,0 @@
1
- "use client";
2
-
3
- import Link from "next/link";
4
- import { ResourceConfig } from "@/config/resources";
5
- import { useResource } from "@/hooks/useResource";
6
- import { Pencil, Trash2 } from "lucide-react";
7
-
8
- interface UserListProps {
9
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
10
- items: any[];
11
- config: ResourceConfig;
12
- }
13
-
14
- import { useState } from "react";
15
- import DeleteModal from "@/components/admin/DeleteModal";
16
-
17
- export default function UserList({ items, config }: UserListProps) {
18
- const { remove, isDeleting } = useResource(config);
19
- const [deleteId, setDeleteId] = useState<string | null>(null);
20
-
21
- const handleDelete = async () => {
22
- if (deleteId) {
23
- const success = await remove(deleteId);
24
- if (success) {
25
- setDeleteId(null);
26
- }
27
- }
28
- };
29
-
30
- return (
31
- <>
32
- <div className="bg-white rounded-lg shadow overflow-hidden">
33
- <div className="overflow-x-auto">
34
- <table className="w-full text-left">
35
- <thead className="bg-gray-50 text-gray-500 uppercase text-xs font-semibold">
36
- <tr>
37
- <th className="px-6 py-3">Email</th>
38
- <th className="px-6 py-3">Full Name</th>
39
- <th className="px-6 py-3">Role</th>
40
- <th className="px-6 py-3 text-right">Actions</th>
41
- </tr>
42
- </thead>
43
- <tbody className="divide-y divide-gray-200">
44
- {items?.map((item) => (
45
- <tr key={item.id} className="hover:bg-gray-50 transition">
46
- <td className="px-6 py-4 text-gray-900 font-medium">{item.email}</td>
47
- <td className="px-6 py-4 text-gray-500">{item.full_name || "-"}</td>
48
- <td className="px-6 py-4">
49
- <span
50
- className={`px-2 py-1 text-xs rounded-full ${
51
- item.role === "admin"
52
- ? "bg-purple-100 text-purple-700"
53
- : "bg-blue-100 text-blue-700"
54
- }`}
55
- >
56
- {item.role}
57
- </span>
58
- </td>
59
- <td className="px-6 py-4 text-right">
60
- <div className="flex justify-end gap-2">
61
- <Link
62
- href={`/users/${item.id}`}
63
- className="flex items-center text-indigo-600 hover:text-indigo-900 bg-indigo-50 hover:bg-indigo-100 px-3 py-1.5 rounded-md transition-colors duration-200"
64
- title="Edit"
65
- >
66
- <Pencil className="w-4 h-4" />
67
- </Link>
68
- <button
69
- onClick={() => setDeleteId(item.id)}
70
- className="flex items-center text-red-600 hover:text-red-900 bg-red-50 hover:bg-red-100 px-3 py-1.5 rounded-md transition-colors duration-200"
71
- title="Delete"
72
- >
73
- <Trash2 className="w-4 h-4" />
74
- </button>
75
- </div>
76
- </td>
77
- </tr>
78
- ))}
79
- {items?.length === 0 && (
80
- <tr>
81
- <td colSpan={4} className="px-6 py-8 text-center text-gray-500">
82
- No users found.
83
- </td>
84
- </tr>
85
- )}
86
- </tbody>
87
- </table>
88
- </div>
89
- </div>
90
-
91
- <DeleteModal
92
- isOpen={!!deleteId}
93
- onClose={() => setDeleteId(null)}
94
- onConfirm={handleDelete}
95
- isLoading={isDeleting}
96
- title="Delete User"
97
- description="Are you sure you want to delete this user? This action cannot be undone."
98
- />
99
- </>
100
- );
101
- }