create-nextjs-stack 0.1.0

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 (123) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +60 -0
  3. package/bin/cli.js +187 -0
  4. package/package.json +48 -0
  5. package/templates/admin/.env.example +11 -0
  6. package/templates/admin/README.md +82 -0
  7. package/templates/admin/app/(auth)/login/page.tsx +84 -0
  8. package/templates/admin/app/(dashboard)/[resource]/[id]/page.tsx +45 -0
  9. package/templates/admin/app/(dashboard)/[resource]/new/page.tsx +32 -0
  10. package/templates/admin/app/(dashboard)/[resource]/page.tsx +131 -0
  11. package/templates/admin/app/(dashboard)/categories/[id]/page.tsx +22 -0
  12. package/templates/admin/app/(dashboard)/categories/new/page.tsx +5 -0
  13. package/templates/admin/app/(dashboard)/categories/page.tsx +33 -0
  14. package/templates/admin/app/(dashboard)/clients/[id]/page.tsx +22 -0
  15. package/templates/admin/app/(dashboard)/clients/new/page.tsx +5 -0
  16. package/templates/admin/app/(dashboard)/clients/page.tsx +33 -0
  17. package/templates/admin/app/(dashboard)/dashboard/page.tsx +45 -0
  18. package/templates/admin/app/(dashboard)/layout.tsx +13 -0
  19. package/templates/admin/app/(dashboard)/products/[id]/page.tsx +22 -0
  20. package/templates/admin/app/(dashboard)/products/new/page.tsx +5 -0
  21. package/templates/admin/app/(dashboard)/products/page.tsx +33 -0
  22. package/templates/admin/app/(dashboard)/projects/[id]/page.tsx +22 -0
  23. package/templates/admin/app/(dashboard)/projects/new/page.tsx +5 -0
  24. package/templates/admin/app/(dashboard)/projects/page.tsx +33 -0
  25. package/templates/admin/app/(dashboard)/users/[id]/page.tsx +22 -0
  26. package/templates/admin/app/(dashboard)/users/new/page.tsx +5 -0
  27. package/templates/admin/app/(dashboard)/users/page.tsx +33 -0
  28. package/templates/admin/app/actions/resources.ts +46 -0
  29. package/templates/admin/app/actions/upload.ts +58 -0
  30. package/templates/admin/app/favicon.ico +0 -0
  31. package/templates/admin/app/globals.css +23 -0
  32. package/templates/admin/app/layout.tsx +23 -0
  33. package/templates/admin/app/page.tsx +5 -0
  34. package/templates/admin/components/admin/AdminLayoutClient.tsx +22 -0
  35. package/templates/admin/components/admin/DeleteModal.tsx +90 -0
  36. package/templates/admin/components/admin/FormLayout.tsx +113 -0
  37. package/templates/admin/components/admin/ImageUpload.tsx +137 -0
  38. package/templates/admin/components/admin/ResourceFormClient.tsx +62 -0
  39. package/templates/admin/components/admin/Sidebar.tsx +74 -0
  40. package/templates/admin/components/admin/SubmitButton.tsx +34 -0
  41. package/templates/admin/components/admin/ToastProvider.tsx +8 -0
  42. package/templates/admin/components/categories/CategoryForm.tsx +24 -0
  43. package/templates/admin/components/categories/CategoryList.tsx +113 -0
  44. package/templates/admin/components/clients/ClientForm.tsx +24 -0
  45. package/templates/admin/components/clients/ClientList.tsx +113 -0
  46. package/templates/admin/components/products/ProductForm.tsx +24 -0
  47. package/templates/admin/components/products/ProductList.tsx +117 -0
  48. package/templates/admin/components/projects/ProjectForm.tsx +24 -0
  49. package/templates/admin/components/projects/ProjectList.tsx +121 -0
  50. package/templates/admin/components/users/UserForm.tsx +39 -0
  51. package/templates/admin/components/users/UserList.tsx +101 -0
  52. package/templates/admin/config/resources.ts +123 -0
  53. package/templates/admin/eslint.config.mjs +18 -0
  54. package/templates/admin/hooks/useResource.ts +86 -0
  55. package/templates/admin/lib/services/base.service.ts +106 -0
  56. package/templates/admin/lib/services/categories.service.ts +7 -0
  57. package/templates/admin/lib/services/clients.service.ts +7 -0
  58. package/templates/admin/lib/services/index.ts +27 -0
  59. package/templates/admin/lib/services/products.service.ts +9 -0
  60. package/templates/admin/lib/services/projects.service.ts +22 -0
  61. package/templates/admin/lib/services/resource.service.ts +26 -0
  62. package/templates/admin/lib/services/users.service.ts +9 -0
  63. package/templates/admin/lib/supabase/client.ts +9 -0
  64. package/templates/admin/lib/supabase/middleware.ts +57 -0
  65. package/templates/admin/lib/supabase/server.ts +29 -0
  66. package/templates/admin/middleware.ts +15 -0
  67. package/templates/admin/next.config.ts +10 -0
  68. package/templates/admin/package-lock.json +6768 -0
  69. package/templates/admin/package.json +33 -0
  70. package/templates/admin/postcss.config.mjs +7 -0
  71. package/templates/admin/public/file.svg +1 -0
  72. package/templates/admin/public/globe.svg +1 -0
  73. package/templates/admin/public/next.svg +1 -0
  74. package/templates/admin/public/vercel.svg +1 -0
  75. package/templates/admin/public/window.svg +1 -0
  76. package/templates/admin/supabase_mock_data.sql +57 -0
  77. package/templates/admin/supabase_schema.sql +93 -0
  78. package/templates/admin/tsconfig.json +34 -0
  79. package/templates/web/.env.example +21 -0
  80. package/templates/web/README.md +129 -0
  81. package/templates/web/components.json +22 -0
  82. package/templates/web/eslint.config.mjs +25 -0
  83. package/templates/web/next.config.ts +25 -0
  84. package/templates/web/package-lock.json +6778 -0
  85. package/templates/web/package.json +45 -0
  86. package/templates/web/postcss.config.mjs +5 -0
  87. package/templates/web/src/app/api/contact/route.ts +181 -0
  88. package/templates/web/src/app/api/revalidate/route.ts +95 -0
  89. package/templates/web/src/app/error.tsx +28 -0
  90. package/templates/web/src/app/globals.css +838 -0
  91. package/templates/web/src/app/layout.tsx +126 -0
  92. package/templates/web/src/app/loading.tsx +60 -0
  93. package/templates/web/src/app/not-found.tsx +68 -0
  94. package/templates/web/src/app/page.tsx +106 -0
  95. package/templates/web/src/app/robots.ts +12 -0
  96. package/templates/web/src/app/sitemap.ts +66 -0
  97. package/templates/web/src/components/home/StatsGrid.tsx +89 -0
  98. package/templates/web/src/hooks/useIntersectionObserver.ts +39 -0
  99. package/templates/web/src/lib/providers/StoreProvider.tsx +12 -0
  100. package/templates/web/src/lib/seo/index.ts +4 -0
  101. package/templates/web/src/lib/seo/metadata.ts +103 -0
  102. package/templates/web/src/lib/seo/seo.config.ts +161 -0
  103. package/templates/web/src/lib/seo/seo.types.ts +76 -0
  104. package/templates/web/src/lib/services/categories.service.ts +38 -0
  105. package/templates/web/src/lib/services/categoryService.ts +251 -0
  106. package/templates/web/src/lib/services/clientService.ts +132 -0
  107. package/templates/web/src/lib/services/clients.service.ts +20 -0
  108. package/templates/web/src/lib/services/productService.ts +261 -0
  109. package/templates/web/src/lib/services/products.service.ts +38 -0
  110. package/templates/web/src/lib/services/projectService.ts +234 -0
  111. package/templates/web/src/lib/services/projects.service.ts +38 -0
  112. package/templates/web/src/lib/services/users.service.ts +20 -0
  113. package/templates/web/src/lib/supabase/client.ts +42 -0
  114. package/templates/web/src/lib/supabase/constants.ts +25 -0
  115. package/templates/web/src/lib/supabase/server.ts +29 -0
  116. package/templates/web/src/lib/supabase/types.ts +112 -0
  117. package/templates/web/src/lib/utils/cache.ts +98 -0
  118. package/templates/web/src/lib/utils/rate-limiter.ts +102 -0
  119. package/templates/web/src/store/actions/index.ts +2 -0
  120. package/templates/web/src/store/index.ts +13 -0
  121. package/templates/web/src/store/reducers/index.ts +13 -0
  122. package/templates/web/src/store/types/index.ts +2 -0
  123. package/templates/web/tsconfig.json +41 -0
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "nextjs-landing-starter",
3
+ "version": "0.1.0",
4
+ "description": "Next.js Landing Page Starter",
5
+ "private": true,
6
+ "scripts": {
7
+ "dev": "next dev --turbopack",
8
+ "build": "next build --turbopack",
9
+ "start": "next start",
10
+ "lint": "eslint"
11
+ },
12
+ "dependencies": {
13
+ "@hookform/resolvers": "^5.2.2",
14
+ "@next/third-parties": "^16.1.1",
15
+ "@supabase/ssr": "^0.8.0",
16
+ "@supabase/supabase-js": "^2.94.1",
17
+ "@vercel/analytics": "^1.6.1",
18
+ "cloudinary": "^2.9.0",
19
+ "framer-motion": "^12.23.26",
20
+ "lucide-react": "^0.562.0",
21
+ "next": "^16.1.6",
22
+ "next-cloudinary": "^6.17.5",
23
+ "react": "^19.2.4",
24
+ "react-dom": "^19.2.4",
25
+ "react-hook-form": "^7.70.0",
26
+ "react-icons": "^5.5.0",
27
+ "react-toastify": "^11.0.5",
28
+ "resend": "^6.6.0",
29
+ "zod": "^4.3.5"
30
+ },
31
+ "devDependencies": {
32
+ "@eslint/eslintrc": "^3",
33
+ "@tailwindcss/postcss": "^4",
34
+ "@types/node": "^20",
35
+ "@types/react": "^19",
36
+ "@types/react-dom": "^19",
37
+ "@types/react-redux": "^7.1.34",
38
+ "@types/redux-logger": "^3.0.13",
39
+ "eslint": "^9",
40
+ "eslint-config-next": "15.5.9",
41
+ "tailwindcss": "^4",
42
+ "tw-animate-css": "^1.4.0",
43
+ "typescript": "^5"
44
+ }
45
+ }
@@ -0,0 +1,5 @@
1
+ const config = {
2
+ plugins: ["@tailwindcss/postcss"],
3
+ };
4
+
5
+ export default config;
@@ -0,0 +1,181 @@
1
+ import { NextResponse } from 'next/server';
2
+ import { Resend } from 'resend';
3
+ import { z } from 'zod';
4
+
5
+ // Validate environment variable
6
+ if (!process.env.RESEND_API_KEY) {
7
+ console.error('RESEND_API_KEY is not set in environment variables');
8
+ }
9
+
10
+ // Initialize Resend with API key
11
+ const resend = new Resend(process.env.RESEND_API_KEY);
12
+
13
+ // Validation schema
14
+ const contactFormSchema = z.object({
15
+ firstName: z.string().min(1, 'First name is required').max(50),
16
+ lastName: z.string().min(1, 'Last name is required').max(50),
17
+ email: z.string().email('Invalid email address'),
18
+ message: z.string().min(10, 'Message must be at least 10 characters').max(1000),
19
+ });
20
+
21
+ // Email template
22
+ const createEmailHtml = (data: {
23
+ firstName: string;
24
+ lastName: string;
25
+ email: string;
26
+ message: string;
27
+ }) => `
28
+ <!DOCTYPE html>
29
+ <html>
30
+ <head>
31
+ <meta charset="utf-8">
32
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
33
+ <title>New Contact Form Submission</title>
34
+ </head>
35
+ <body style="font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background-color: #f5f5f5; margin: 0; padding: 20px;">
36
+ <table width="100%" cellpadding="0" cellspacing="0" style="max-width: 600px; margin: 0 auto; background-color: #ffffff; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1);">
37
+ <!-- Header -->
38
+ <tr>
39
+ <td style="background: linear-gradient(135deg, #000000 0%, #1a1a1a 100%); padding: 40px 20px; text-align: center;">
40
+ <h1 style="color: #ffffff; margin: 0; font-size: 24px; font-weight: bold; letter-spacing: 1px;">
41
+ EURODECO PANEL SYSTEMS
42
+ </h1>
43
+ <p style="color: #a0a0a0; margin: 10px 0 0; font-size: 14px;">
44
+ New Contact Form Submission
45
+ </p>
46
+ </td>
47
+ </tr>
48
+
49
+ <!-- Content -->
50
+ <tr>
51
+ <td style="padding: 40px 30px;">
52
+ <h2 style="color: #1a1a1a; margin: 0 0 24px; font-size: 20px; font-weight: 600;">
53
+ Contact Details
54
+ </h2>
55
+
56
+ <!-- Contact Info -->
57
+ <table width="100%" cellpadding="0" cellspacing="0" style="margin-bottom: 30px;">
58
+ <tr>
59
+ <td style="padding: 12px 0; border-bottom: 1px solid #e5e5e5;">
60
+ <strong style="color: #666666; display: inline-block; width: 100px; font-size: 14px;">Name:</strong>
61
+ <span style="color: #1a1a1a; font-size: 14px;">${data.firstName} ${data.lastName}</span>
62
+ </td>
63
+ </tr>
64
+ <tr>
65
+ <td style="padding: 12px 0; border-bottom: 1px solid #e5e5e5;">
66
+ <strong style="color: #666666; display: inline-block; width: 100px; font-size: 14px;">Email:</strong>
67
+ <a href="mailto:${data.email}" style="color: #3b82f6; text-decoration: none; font-size: 14px;">${data.email}</a>
68
+ </td>
69
+ </tr>
70
+ </table>
71
+
72
+ <!-- Message -->
73
+ <h3 style="color: #1a1a1a; margin: 0 0 16px; font-size: 16px; font-weight: 600;">Message:</h3>
74
+ <div style="background-color: #f9fafb; border-left: 4px solid #3b82f6; padding: 20px; border-radius: 4px;">
75
+ <p style="color: #4b5563; margin: 0; line-height: 1.6; white-space: pre-wrap; font-size: 14px;">${data.message}</p>
76
+ </div>
77
+ </td>
78
+ </tr>
79
+
80
+ <!-- Footer -->
81
+ <tr>
82
+ <td style="background-color: #f9fafb; padding: 20px 30px; text-align: center; border-top: 1px solid #e5e5e5;">
83
+ <p style="color: #9ca3af; margin: 0; font-size: 12px;">
84
+ This email was sent from the Eurodeco contact form
85
+ </p>
86
+ <p style="color: #9ca3af; margin: 8px 0 0; font-size: 12px;">
87
+ Received on ${new Date().toLocaleString('de-DE', { timeZone: 'Europe/Berlin' })}
88
+ </p>
89
+ </td>
90
+ </tr>
91
+ </table>
92
+ </body>
93
+ </html>
94
+ `;
95
+
96
+ // Plain text version
97
+ const createEmailText = (data: {
98
+ firstName: string;
99
+ lastName: string;
100
+ email: string;
101
+ message: string;
102
+ }) => `
103
+ EURODECO PANEL SYSTEMS - New Contact Form Submission
104
+
105
+ Contact Details:
106
+ Name: ${data.firstName} ${data.lastName}
107
+ Email: ${data.email}
108
+
109
+ Message:
110
+ ${data.message}
111
+
112
+ ---
113
+ This email was sent from the Eurodeco contact form
114
+ Received on ${new Date().toLocaleString('de-DE', { timeZone: 'Europe/Berlin' })}
115
+ `;
116
+
117
+ export async function POST(request: Request) {
118
+ try {
119
+ // Parse request body
120
+ const body = await request.json();
121
+
122
+ // Validate input
123
+ const validationResult = contactFormSchema.safeParse(body);
124
+
125
+ if (!validationResult.success) {
126
+ return NextResponse.json(
127
+ {
128
+ error: 'Validation failed',
129
+ details: validationResult.error.issues
130
+ },
131
+ { status: 400 }
132
+ );
133
+ }
134
+
135
+ const data = validationResult.data;
136
+
137
+ // Send email using Resend
138
+ const emailResponse = await resend.emails.send({
139
+ from: process.env.RESEND_FROM_EMAIL || 'onboarding@resend.dev',
140
+ to: process.env.CONTACT_EMAIL || 'info@eurodecopanel.de',
141
+ replyTo: data.email,
142
+ subject: `New Contact Form Submission from ${data.firstName} ${data.lastName}`,
143
+ html: createEmailHtml(data),
144
+ text: createEmailText(data),
145
+ });
146
+
147
+ console.log('Email sent successfully:', emailResponse);
148
+
149
+ // Check for errors in Resend response
150
+ if (emailResponse.error) {
151
+ console.error('Resend API Error:', emailResponse.error);
152
+ return NextResponse.json(
153
+ { error: 'Failed to send email. Please try again later.' },
154
+ { status: 500 }
155
+ );
156
+ }
157
+
158
+ // Success response
159
+ return NextResponse.json(
160
+ {
161
+ success: true,
162
+ message: 'Email sent successfully',
163
+ id: emailResponse.data?.id
164
+ },
165
+ { status: 200 }
166
+ );
167
+
168
+ } catch (error) {
169
+ console.error('Contact form error:', error);
170
+
171
+ return NextResponse.json(
172
+ { error: 'An unexpected error occurred. Please try again later.' },
173
+ { status: 500 }
174
+ );
175
+ }
176
+ }
177
+
178
+ // Handle OPTIONS for CORS preflight
179
+ export async function OPTIONS() {
180
+ return NextResponse.json({}, { status: 200 });
181
+ }
@@ -0,0 +1,95 @@
1
+ import { NextRequest, NextResponse } from 'next/server';
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
+
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
+ 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}`);
63
+ }
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
+ }
88
+
89
+ // Optional: GET endpoint to check if the API is working
90
+ export async function GET() {
91
+ return NextResponse.json({
92
+ message: 'Revalidation API is running',
93
+ usage: 'POST with { secret, tag?, path? }',
94
+ });
95
+ }
@@ -0,0 +1,28 @@
1
+ 'use client';
2
+
3
+ import { useEffect } from 'react';
4
+
5
+ export default function Error({
6
+ error,
7
+ reset,
8
+ }: {
9
+ error: Error & { digest?: string };
10
+ reset: () => void;
11
+ }) {
12
+ useEffect(() => {
13
+ console.error('Error:', error);
14
+ }, [error]);
15
+
16
+ return (
17
+ <div className="flex flex-col items-center justify-center min-h-screen bg-black text-white px-4 font-outfit">
18
+ <h2 className="text-4xl font-bold mb-4">Something went wrong!</h2>
19
+ <p className="text-white/60 mb-8">We&apos;re sorry for the inconvenience.</p>
20
+ <button
21
+ onClick={reset}
22
+ className="px-6 py-3 bg-red-600 hover:bg-red-700 transition-colors font-bold rounded-md"
23
+ >
24
+ Try again
25
+ </button>
26
+ </div>
27
+ );
28
+ }