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.
- package/LICENSE +21 -0
- package/README.md +60 -0
- package/bin/cli.js +187 -0
- package/package.json +48 -0
- package/templates/admin/.env.example +11 -0
- package/templates/admin/README.md +82 -0
- package/templates/admin/app/(auth)/login/page.tsx +84 -0
- package/templates/admin/app/(dashboard)/[resource]/[id]/page.tsx +45 -0
- package/templates/admin/app/(dashboard)/[resource]/new/page.tsx +32 -0
- package/templates/admin/app/(dashboard)/[resource]/page.tsx +131 -0
- package/templates/admin/app/(dashboard)/categories/[id]/page.tsx +22 -0
- package/templates/admin/app/(dashboard)/categories/new/page.tsx +5 -0
- package/templates/admin/app/(dashboard)/categories/page.tsx +33 -0
- package/templates/admin/app/(dashboard)/clients/[id]/page.tsx +22 -0
- package/templates/admin/app/(dashboard)/clients/new/page.tsx +5 -0
- package/templates/admin/app/(dashboard)/clients/page.tsx +33 -0
- package/templates/admin/app/(dashboard)/dashboard/page.tsx +45 -0
- package/templates/admin/app/(dashboard)/layout.tsx +13 -0
- package/templates/admin/app/(dashboard)/products/[id]/page.tsx +22 -0
- package/templates/admin/app/(dashboard)/products/new/page.tsx +5 -0
- package/templates/admin/app/(dashboard)/products/page.tsx +33 -0
- package/templates/admin/app/(dashboard)/projects/[id]/page.tsx +22 -0
- package/templates/admin/app/(dashboard)/projects/new/page.tsx +5 -0
- package/templates/admin/app/(dashboard)/projects/page.tsx +33 -0
- package/templates/admin/app/(dashboard)/users/[id]/page.tsx +22 -0
- package/templates/admin/app/(dashboard)/users/new/page.tsx +5 -0
- package/templates/admin/app/(dashboard)/users/page.tsx +33 -0
- package/templates/admin/app/actions/resources.ts +46 -0
- package/templates/admin/app/actions/upload.ts +58 -0
- package/templates/admin/app/favicon.ico +0 -0
- package/templates/admin/app/globals.css +23 -0
- package/templates/admin/app/layout.tsx +23 -0
- package/templates/admin/app/page.tsx +5 -0
- package/templates/admin/components/admin/AdminLayoutClient.tsx +22 -0
- package/templates/admin/components/admin/DeleteModal.tsx +90 -0
- package/templates/admin/components/admin/FormLayout.tsx +113 -0
- package/templates/admin/components/admin/ImageUpload.tsx +137 -0
- package/templates/admin/components/admin/ResourceFormClient.tsx +62 -0
- package/templates/admin/components/admin/Sidebar.tsx +74 -0
- package/templates/admin/components/admin/SubmitButton.tsx +34 -0
- package/templates/admin/components/admin/ToastProvider.tsx +8 -0
- package/templates/admin/components/categories/CategoryForm.tsx +24 -0
- package/templates/admin/components/categories/CategoryList.tsx +113 -0
- package/templates/admin/components/clients/ClientForm.tsx +24 -0
- package/templates/admin/components/clients/ClientList.tsx +113 -0
- package/templates/admin/components/products/ProductForm.tsx +24 -0
- package/templates/admin/components/products/ProductList.tsx +117 -0
- package/templates/admin/components/projects/ProjectForm.tsx +24 -0
- package/templates/admin/components/projects/ProjectList.tsx +121 -0
- package/templates/admin/components/users/UserForm.tsx +39 -0
- package/templates/admin/components/users/UserList.tsx +101 -0
- package/templates/admin/config/resources.ts +123 -0
- package/templates/admin/eslint.config.mjs +18 -0
- package/templates/admin/hooks/useResource.ts +86 -0
- package/templates/admin/lib/services/base.service.ts +106 -0
- package/templates/admin/lib/services/categories.service.ts +7 -0
- package/templates/admin/lib/services/clients.service.ts +7 -0
- package/templates/admin/lib/services/index.ts +27 -0
- package/templates/admin/lib/services/products.service.ts +9 -0
- package/templates/admin/lib/services/projects.service.ts +22 -0
- package/templates/admin/lib/services/resource.service.ts +26 -0
- package/templates/admin/lib/services/users.service.ts +9 -0
- package/templates/admin/lib/supabase/client.ts +9 -0
- package/templates/admin/lib/supabase/middleware.ts +57 -0
- package/templates/admin/lib/supabase/server.ts +29 -0
- package/templates/admin/middleware.ts +15 -0
- package/templates/admin/next.config.ts +10 -0
- package/templates/admin/package-lock.json +6768 -0
- package/templates/admin/package.json +33 -0
- package/templates/admin/postcss.config.mjs +7 -0
- package/templates/admin/public/file.svg +1 -0
- package/templates/admin/public/globe.svg +1 -0
- package/templates/admin/public/next.svg +1 -0
- package/templates/admin/public/vercel.svg +1 -0
- package/templates/admin/public/window.svg +1 -0
- package/templates/admin/supabase_mock_data.sql +57 -0
- package/templates/admin/supabase_schema.sql +93 -0
- package/templates/admin/tsconfig.json +34 -0
- package/templates/web/.env.example +21 -0
- package/templates/web/README.md +129 -0
- package/templates/web/components.json +22 -0
- package/templates/web/eslint.config.mjs +25 -0
- package/templates/web/next.config.ts +25 -0
- package/templates/web/package-lock.json +6778 -0
- package/templates/web/package.json +45 -0
- package/templates/web/postcss.config.mjs +5 -0
- package/templates/web/src/app/api/contact/route.ts +181 -0
- package/templates/web/src/app/api/revalidate/route.ts +95 -0
- package/templates/web/src/app/error.tsx +28 -0
- package/templates/web/src/app/globals.css +838 -0
- package/templates/web/src/app/layout.tsx +126 -0
- package/templates/web/src/app/loading.tsx +60 -0
- package/templates/web/src/app/not-found.tsx +68 -0
- package/templates/web/src/app/page.tsx +106 -0
- package/templates/web/src/app/robots.ts +12 -0
- package/templates/web/src/app/sitemap.ts +66 -0
- package/templates/web/src/components/home/StatsGrid.tsx +89 -0
- package/templates/web/src/hooks/useIntersectionObserver.ts +39 -0
- package/templates/web/src/lib/providers/StoreProvider.tsx +12 -0
- package/templates/web/src/lib/seo/index.ts +4 -0
- package/templates/web/src/lib/seo/metadata.ts +103 -0
- package/templates/web/src/lib/seo/seo.config.ts +161 -0
- package/templates/web/src/lib/seo/seo.types.ts +76 -0
- package/templates/web/src/lib/services/categories.service.ts +38 -0
- package/templates/web/src/lib/services/categoryService.ts +251 -0
- package/templates/web/src/lib/services/clientService.ts +132 -0
- package/templates/web/src/lib/services/clients.service.ts +20 -0
- package/templates/web/src/lib/services/productService.ts +261 -0
- package/templates/web/src/lib/services/products.service.ts +38 -0
- package/templates/web/src/lib/services/projectService.ts +234 -0
- package/templates/web/src/lib/services/projects.service.ts +38 -0
- package/templates/web/src/lib/services/users.service.ts +20 -0
- package/templates/web/src/lib/supabase/client.ts +42 -0
- package/templates/web/src/lib/supabase/constants.ts +25 -0
- package/templates/web/src/lib/supabase/server.ts +29 -0
- package/templates/web/src/lib/supabase/types.ts +112 -0
- package/templates/web/src/lib/utils/cache.ts +98 -0
- package/templates/web/src/lib/utils/rate-limiter.ts +102 -0
- package/templates/web/src/store/actions/index.ts +2 -0
- package/templates/web/src/store/index.ts +13 -0
- package/templates/web/src/store/reducers/index.ts +13 -0
- package/templates/web/src/store/types/index.ts +2 -0
- 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,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'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
|
+
}
|