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
|
@@ -1,95 +1,54 @@
|
|
|
1
1
|
import { NextRequest, NextResponse } from 'next/server';
|
|
2
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
3
|
|
|
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
4
|
export async function POST(request: NextRequest) {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
revalidated.push(`path:${path}`);
|
|
5
|
+
try {
|
|
6
|
+
const body = await request.json();
|
|
7
|
+
const { secret, tag, path } = body;
|
|
8
|
+
|
|
9
|
+
if (!secret || secret !== process.env.REVALIDATION_SECRET) {
|
|
10
|
+
return NextResponse.json(
|
|
11
|
+
{ error: 'Invalid or missing secret' },
|
|
12
|
+
{ status: 401 }
|
|
13
|
+
);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const revalidated: string[] = [];
|
|
17
|
+
|
|
18
|
+
if (tag) {
|
|
19
|
+
revalidateTag(tag, 'everything');
|
|
20
|
+
revalidated.push(`tag:${tag}`);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (path) {
|
|
24
|
+
revalidatePath(path);
|
|
25
|
+
revalidated.push(`path:${path}`);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (revalidated.length === 0) {
|
|
29
|
+
return NextResponse.json(
|
|
30
|
+
{ error: 'No tag or path specified' },
|
|
31
|
+
{ status: 400 }
|
|
32
|
+
);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
return NextResponse.json({
|
|
36
|
+
revalidated: true,
|
|
37
|
+
items: revalidated,
|
|
38
|
+
timestamp: new Date().toISOString(),
|
|
39
|
+
});
|
|
40
|
+
} catch (error) {
|
|
41
|
+
console.error('[Revalidation] Error:', error);
|
|
42
|
+
return NextResponse.json(
|
|
43
|
+
{ error: 'Error revalidating', message: (error as Error).message },
|
|
44
|
+
{ status: 500 }
|
|
45
|
+
);
|
|
63
46
|
}
|
|
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
47
|
}
|
|
88
48
|
|
|
89
|
-
// Optional: GET endpoint to check if the API is working
|
|
90
49
|
export async function GET() {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
}
|
|
50
|
+
return NextResponse.json({
|
|
51
|
+
message: 'Revalidation API is running',
|
|
52
|
+
usage: 'POST with { secret, tag?, path? }',
|
|
53
|
+
});
|
|
54
|
+
}
|
|
@@ -50,19 +50,7 @@
|
|
|
50
50
|
--radius-3xl: calc(var(--radius) + 12px);
|
|
51
51
|
--radius-4xl: calc(var(--radius) + 16px);
|
|
52
52
|
|
|
53
|
-
/* Z-Index
|
|
54
|
-
--z-base: 1;
|
|
55
|
-
--z-dropdown: 1000;
|
|
56
|
-
--z-sticky: 1020;
|
|
57
|
-
--z-fixed: 1030;
|
|
58
|
-
--z-modal-backdrop: 1040;
|
|
59
|
-
--z-modal: 1050;
|
|
60
|
-
--z-popover: 1060;
|
|
61
|
-
--z-tooltip: 1070;
|
|
62
|
-
--z-lightbox: 9999;
|
|
63
|
-
--z-lightbox-controls: 10000;
|
|
64
|
-
|
|
65
|
-
/* Z-Index Utilities */
|
|
53
|
+
/* Z-Index Scale */
|
|
66
54
|
--z-base: 1;
|
|
67
55
|
--z-dropdown: 1000;
|
|
68
56
|
--z-sticky: 1020;
|
|
@@ -1,59 +1,17 @@
|
|
|
1
1
|
import type { Metadata, Viewport } from "next";
|
|
2
2
|
import {
|
|
3
|
-
Geist,
|
|
4
|
-
Geist_Mono,
|
|
5
3
|
Inter,
|
|
6
|
-
Outfit,
|
|
7
|
-
Raleway,
|
|
8
|
-
Roboto,
|
|
9
|
-
Jersey_10,
|
|
10
4
|
} from "next/font/google";
|
|
11
5
|
import "@/app/globals.css";
|
|
12
6
|
import StoreProvider from "@/lib/providers/StoreProvider";
|
|
13
7
|
import { GoogleAnalytics } from "@next/third-parties/google";
|
|
14
8
|
|
|
15
|
-
const geistSans = Geist({
|
|
16
|
-
variable: "--font-geist-sans",
|
|
17
|
-
subsets: ["latin"],
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
const geistMono = Geist_Mono({
|
|
21
|
-
variable: "--font-geist-mono",
|
|
22
|
-
subsets: ["latin"],
|
|
23
|
-
});
|
|
24
|
-
|
|
25
9
|
const inter = Inter({
|
|
26
10
|
variable: "--font-inter",
|
|
27
11
|
subsets: ["latin"],
|
|
28
12
|
display: "swap",
|
|
29
13
|
});
|
|
30
14
|
|
|
31
|
-
const roboto = Roboto({
|
|
32
|
-
variable: "--font-roboto",
|
|
33
|
-
subsets: ["latin"],
|
|
34
|
-
weight: ["100", "300", "400", "500", "700", "900"],
|
|
35
|
-
display: "swap",
|
|
36
|
-
});
|
|
37
|
-
|
|
38
|
-
const outfit = Outfit({
|
|
39
|
-
variable: "--font-outfit",
|
|
40
|
-
subsets: ["latin"],
|
|
41
|
-
display: "swap",
|
|
42
|
-
});
|
|
43
|
-
|
|
44
|
-
const raleway = Raleway({
|
|
45
|
-
variable: "--font-raleway",
|
|
46
|
-
subsets: ["latin"],
|
|
47
|
-
display: "swap",
|
|
48
|
-
});
|
|
49
|
-
|
|
50
|
-
const jersey10 = Jersey_10({
|
|
51
|
-
variable: "--font-jersey-10",
|
|
52
|
-
subsets: ["latin"],
|
|
53
|
-
weight: "400",
|
|
54
|
-
display: "swap",
|
|
55
|
-
});
|
|
56
|
-
|
|
57
15
|
export const viewport: Viewport = {
|
|
58
16
|
themeColor: [
|
|
59
17
|
{ media: "(prefers-color-scheme: light)", color: "#ffffff" },
|
|
@@ -65,7 +23,7 @@ export const viewport: Viewport = {
|
|
|
65
23
|
};
|
|
66
24
|
|
|
67
25
|
export const metadata: Metadata = {
|
|
68
|
-
metadataBase: new URL(
|
|
26
|
+
metadataBase: new URL(process.env.NEXT_PUBLIC_SITE_URL || "http://localhost:3000"),
|
|
69
27
|
title: {
|
|
70
28
|
default: "Next.js Enterprise Starter",
|
|
71
29
|
template: "%s | Next.js Enterprise Starter",
|
|
@@ -89,7 +47,7 @@ export const metadata: Metadata = {
|
|
|
89
47
|
shortcut: "/favicon.png",
|
|
90
48
|
},
|
|
91
49
|
alternates: {
|
|
92
|
-
canonical:
|
|
50
|
+
canonical: process.env.NEXT_PUBLIC_SITE_URL || "http://localhost:3000",
|
|
93
51
|
},
|
|
94
52
|
openGraph: {
|
|
95
53
|
locale: "en_US",
|
|
@@ -114,11 +72,11 @@ export default function RootLayout({
|
|
|
114
72
|
return (
|
|
115
73
|
<html lang="en">
|
|
116
74
|
<body
|
|
117
|
-
className={`${
|
|
75
|
+
className={`${inter.variable} antialiased`}
|
|
118
76
|
>
|
|
119
77
|
<StoreProvider>
|
|
120
78
|
{children}
|
|
121
|
-
<GoogleAnalytics gaId=
|
|
79
|
+
{process.env.NEXT_PUBLIC_GA_ID && <GoogleAnalytics gaId={process.env.NEXT_PUBLIC_GA_ID} />}
|
|
122
80
|
</StoreProvider>
|
|
123
81
|
</body>
|
|
124
82
|
</html>
|
|
@@ -7,6 +7,6 @@ export default function robots(): MetadataRoute.Robots {
|
|
|
7
7
|
allow: '/',
|
|
8
8
|
disallow: ['/api/', '/_next/'],
|
|
9
9
|
},
|
|
10
|
-
sitemap: '
|
|
10
|
+
sitemap: `${process.env.NEXT_PUBLIC_SITE_URL || 'http://localhost:3000'}/sitemap.xml`,
|
|
11
11
|
};
|
|
12
12
|
}
|
|
@@ -1,43 +1,35 @@
|
|
|
1
1
|
import { MetadataRoute } from 'next';
|
|
2
|
-
import {
|
|
3
|
-
import { productService } from '@/lib/services/productService';
|
|
2
|
+
import { getAdminClient } from '@/lib/supabase/server';
|
|
4
3
|
|
|
5
4
|
export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|
6
|
-
const baseUrl = 'https://
|
|
5
|
+
const baseUrl = process.env.NEXT_PUBLIC_SITE_URL || 'https://example.com';
|
|
7
6
|
|
|
8
7
|
// Static pages - CUSTOMIZE THESE ROUTES FOR YOUR PROJECT
|
|
9
8
|
const routes = [
|
|
10
9
|
'',
|
|
11
10
|
'/about',
|
|
12
|
-
'/products',
|
|
13
|
-
'/innovation',
|
|
14
|
-
'/services',
|
|
15
|
-
'/investors',
|
|
16
|
-
'/references',
|
|
17
11
|
'/contact',
|
|
18
|
-
'/impressum',
|
|
19
|
-
'/datenschutz',
|
|
20
|
-
'/agb'
|
|
21
12
|
];
|
|
22
13
|
|
|
23
|
-
const
|
|
14
|
+
const entries: MetadataRoute.Sitemap = routes.map((route) => ({
|
|
15
|
+
url: `${baseUrl}${route}`,
|
|
16
|
+
lastModified: new Date(),
|
|
17
|
+
changeFrequency: route === '' ? 'daily' : 'weekly',
|
|
18
|
+
priority: route === '' ? 1.0 : route === '/contact' ? 0.9 : 0.8,
|
|
19
|
+
}));
|
|
24
20
|
|
|
25
|
-
|
|
26
|
-
routes.forEach((route) => {
|
|
27
|
-
sitemap.push({
|
|
28
|
-
url: `${baseUrl}${route}`,
|
|
29
|
-
lastModified: new Date(),
|
|
30
|
-
changeFrequency: route === '' ? 'daily' : 'weekly',
|
|
31
|
-
priority: route === '' ? 1.0 : (route === '/contact' ? 0.9 : 0.8),
|
|
32
|
-
});
|
|
33
|
-
});
|
|
21
|
+
const supabase = await getAdminClient();
|
|
34
22
|
|
|
35
|
-
// Dynamic Projects
|
|
23
|
+
// Dynamic Projects - CUSTOMIZE TABLE AND SLUG FIELD FOR YOUR PROJECT
|
|
36
24
|
try {
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
25
|
+
const { data: projects } = await supabase
|
|
26
|
+
.from('projects')
|
|
27
|
+
.select('slug')
|
|
28
|
+
.eq('published', true);
|
|
29
|
+
|
|
30
|
+
projects?.forEach(({ slug }) => {
|
|
31
|
+
entries.push({
|
|
32
|
+
url: `${baseUrl}/projects/${slug}`,
|
|
41
33
|
lastModified: new Date(),
|
|
42
34
|
changeFrequency: 'weekly',
|
|
43
35
|
priority: 0.7,
|
|
@@ -47,11 +39,15 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|
|
47
39
|
console.error('[Sitemap] Error fetching project slugs:', error);
|
|
48
40
|
}
|
|
49
41
|
|
|
50
|
-
// Dynamic Products
|
|
42
|
+
// Dynamic Products - CUSTOMIZE TABLE AND SLUG FIELD FOR YOUR PROJECT
|
|
51
43
|
try {
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
44
|
+
const { data: products } = await supabase
|
|
45
|
+
.from('products')
|
|
46
|
+
.select('slug')
|
|
47
|
+
.eq('published', true);
|
|
48
|
+
|
|
49
|
+
products?.forEach(({ slug }) => {
|
|
50
|
+
entries.push({
|
|
55
51
|
url: `${baseUrl}/products/${slug}`,
|
|
56
52
|
lastModified: new Date(),
|
|
57
53
|
changeFrequency: 'weekly',
|
|
@@ -62,5 +58,5 @@ export default async function sitemap(): Promise<MetadataRoute.Sitemap> {
|
|
|
62
58
|
console.error('[Sitemap] Error fetching product slugs:', error);
|
|
63
59
|
}
|
|
64
60
|
|
|
65
|
-
return
|
|
61
|
+
return entries;
|
|
66
62
|
}
|
|
@@ -88,16 +88,16 @@ export function generateOrganizationSchema() {
|
|
|
88
88
|
url: site.siteUrl,
|
|
89
89
|
logo: `${site.siteUrl}/images/logo.png`,
|
|
90
90
|
description:
|
|
91
|
-
'
|
|
91
|
+
'Your Company Name - Generic description for your business',
|
|
92
92
|
contactPoint: {
|
|
93
93
|
'@type': 'ContactPoint',
|
|
94
94
|
contactType: 'Sales',
|
|
95
|
-
availableLanguage: ['
|
|
95
|
+
availableLanguage: ['English', 'German'],
|
|
96
96
|
},
|
|
97
97
|
sameAs: [
|
|
98
|
-
//
|
|
99
|
-
// 'https://www.linkedin.com/company/
|
|
100
|
-
// 'https://www.instagram.com/
|
|
98
|
+
// Add your social media links here
|
|
99
|
+
// 'https://www.linkedin.com/company/yourcompany',
|
|
100
|
+
// 'https://www.instagram.com/yourcompany',
|
|
101
101
|
],
|
|
102
102
|
};
|
|
103
103
|
}
|
|
@@ -6,42 +6,39 @@ import { SEOConfig } from './seo.types';
|
|
|
6
6
|
*/
|
|
7
7
|
export const seoConfig: SEOConfig = {
|
|
8
8
|
site: {
|
|
9
|
-
siteName: '
|
|
10
|
-
siteUrl: 'https://www.
|
|
11
|
-
defaultLocale: '
|
|
12
|
-
locales: ['
|
|
9
|
+
siteName: 'Your Company Name',
|
|
10
|
+
siteUrl: 'https://www.yourdomain.com',
|
|
11
|
+
defaultLocale: 'en',
|
|
12
|
+
locales: ['en', 'de'],
|
|
13
13
|
defaultOGImage: '/images/og-default.jpg',
|
|
14
|
-
twitterHandle: '@
|
|
14
|
+
twitterHandle: '@yourhandle', // Placeholder handle
|
|
15
15
|
},
|
|
16
16
|
|
|
17
17
|
pages: {
|
|
18
18
|
// Home Page SEO
|
|
19
19
|
home: {
|
|
20
|
-
title: '
|
|
20
|
+
title: 'Your Company Name - Catchy Slogan Here',
|
|
21
21
|
description:
|
|
22
|
-
'
|
|
22
|
+
'Your company description goes here. Explain what you do, who you serve, and why you are the best choice for your customers.',
|
|
23
23
|
keywords: [
|
|
24
|
-
'
|
|
25
|
-
'
|
|
26
|
-
'
|
|
27
|
-
'
|
|
28
|
-
'
|
|
29
|
-
'Leichtbau',
|
|
30
|
-
'Architektur',
|
|
31
|
-
'Gewerbeprojekte',
|
|
24
|
+
'Keyword 1',
|
|
25
|
+
'Keyword 2',
|
|
26
|
+
'Keyword 3',
|
|
27
|
+
'Keyword 4',
|
|
28
|
+
'Keyword 5',
|
|
32
29
|
],
|
|
33
30
|
openGraph: {
|
|
34
|
-
title: '
|
|
31
|
+
title: 'Your Company Name - Catchy Slogan Here',
|
|
35
32
|
description:
|
|
36
|
-
'
|
|
33
|
+
'Your short company description for social media sharing goes here.',
|
|
37
34
|
images: ['/images/og-home.jpg'],
|
|
38
35
|
type: 'website',
|
|
39
36
|
},
|
|
40
37
|
twitter: {
|
|
41
38
|
card: 'summary_large_image',
|
|
42
|
-
title: '
|
|
39
|
+
title: 'Your Company Name - Catchy Slogan Here',
|
|
43
40
|
description:
|
|
44
|
-
'
|
|
41
|
+
'Your short company description for Twitter sharing goes here.',
|
|
45
42
|
images: ['/images/twitter-home.jpg'],
|
|
46
43
|
},
|
|
47
44
|
robots: {
|
|
@@ -56,22 +53,21 @@ export const seoConfig: SEOConfig = {
|
|
|
56
53
|
|
|
57
54
|
// About Page SEO
|
|
58
55
|
about: {
|
|
59
|
-
title: '
|
|
56
|
+
title: 'About Us - Your Company Name',
|
|
60
57
|
description:
|
|
61
|
-
'
|
|
58
|
+
'Learn more about Your Company Name, our mission, vision, and the technology behind our products and services.',
|
|
62
59
|
keywords: [
|
|
63
|
-
'
|
|
64
|
-
'Mission',
|
|
65
|
-
'Vision',
|
|
66
|
-
'
|
|
67
|
-
'
|
|
68
|
-
'Unternehmensprofil',
|
|
60
|
+
'About Us',
|
|
61
|
+
'Company Mission',
|
|
62
|
+
'Company Vision',
|
|
63
|
+
'Our Technology',
|
|
64
|
+
'Company Profile',
|
|
69
65
|
],
|
|
70
|
-
canonical: 'https://www.
|
|
66
|
+
canonical: 'https://www.yourdomain.com/about',
|
|
71
67
|
openGraph: {
|
|
72
|
-
title: '
|
|
68
|
+
title: 'About Us - Your Company Name',
|
|
73
69
|
description:
|
|
74
|
-
'
|
|
70
|
+
'Learn more about Your Company Name and our innovative solutions.',
|
|
75
71
|
images: ['/images/og-about.jpg'],
|
|
76
72
|
type: 'website',
|
|
77
73
|
},
|
|
@@ -83,21 +79,21 @@ export const seoConfig: SEOConfig = {
|
|
|
83
79
|
|
|
84
80
|
// Works/Projects Page SEO
|
|
85
81
|
works: {
|
|
86
|
-
title: '
|
|
82
|
+
title: 'Our Work - Your Company Name',
|
|
87
83
|
description:
|
|
88
|
-
'
|
|
84
|
+
'Discover our successful projects and references across various industries and domains.',
|
|
89
85
|
keywords: [
|
|
90
|
-
'
|
|
91
|
-
'
|
|
92
|
-
'
|
|
93
|
-
'
|
|
94
|
-
'
|
|
86
|
+
'References',
|
|
87
|
+
'Projects',
|
|
88
|
+
'Our Work',
|
|
89
|
+
'Portfolio',
|
|
90
|
+
'Case Studies',
|
|
95
91
|
],
|
|
96
|
-
canonical: 'https://www.
|
|
92
|
+
canonical: 'https://www.yourdomain.com/references',
|
|
97
93
|
openGraph: {
|
|
98
|
-
title: '
|
|
94
|
+
title: 'Our Work - Your Company Name',
|
|
99
95
|
description:
|
|
100
|
-
'
|
|
96
|
+
'Discover our successful projects and references.',
|
|
101
97
|
images: ['/images/og-works.jpg'],
|
|
102
98
|
type: 'website',
|
|
103
99
|
},
|
|
@@ -109,20 +105,20 @@ export const seoConfig: SEOConfig = {
|
|
|
109
105
|
|
|
110
106
|
// Blog Page SEO (Keeping structurally but updating content generic/german if needed, or removing if not in list. Task.md didn't ask to remove, just update config. I'll translate to German generic).
|
|
111
107
|
blog: {
|
|
112
|
-
title: '
|
|
108
|
+
title: 'Blog - Your Company Name',
|
|
113
109
|
description:
|
|
114
|
-
'
|
|
110
|
+
'News, trends, and insights about our industry and innovations from Your Company Name.',
|
|
115
111
|
keywords: [
|
|
116
|
-
'
|
|
117
|
-
'
|
|
118
|
-
'
|
|
119
|
-
'
|
|
112
|
+
'Company News',
|
|
113
|
+
'Industry Trends',
|
|
114
|
+
'Innovations',
|
|
115
|
+
'Blog',
|
|
120
116
|
],
|
|
121
|
-
canonical: 'https://www.
|
|
117
|
+
canonical: 'https://www.yourdomain.com/blog',
|
|
122
118
|
openGraph: {
|
|
123
|
-
title: '
|
|
119
|
+
title: 'Blog - Your Company Name',
|
|
124
120
|
description:
|
|
125
|
-
'
|
|
121
|
+
'News, trends, and insights about our industry and architecture.',
|
|
126
122
|
images: ['/images/og-blog.jpg'],
|
|
127
123
|
type: 'website',
|
|
128
124
|
},
|
|
@@ -134,21 +130,21 @@ export const seoConfig: SEOConfig = {
|
|
|
134
130
|
|
|
135
131
|
// Contact Page SEO
|
|
136
132
|
contact: {
|
|
137
|
-
title: '
|
|
133
|
+
title: 'Contact - Your Company Name',
|
|
138
134
|
description:
|
|
139
|
-
'
|
|
135
|
+
'Contact Your Company Name for your next project. We look forward to hearing from you.',
|
|
140
136
|
keywords: [
|
|
141
|
-
'
|
|
142
|
-
'
|
|
143
|
-
'
|
|
144
|
-
'
|
|
145
|
-
'
|
|
137
|
+
'Contact Us',
|
|
138
|
+
'Inquiry',
|
|
139
|
+
'Start Project',
|
|
140
|
+
'Company Address',
|
|
141
|
+
'Company Email',
|
|
146
142
|
],
|
|
147
|
-
canonical: 'https://www.
|
|
143
|
+
canonical: 'https://www.yourdomain.com/contact',
|
|
148
144
|
openGraph: {
|
|
149
|
-
title: '
|
|
145
|
+
title: 'Contact - Your Company Name',
|
|
150
146
|
description:
|
|
151
|
-
'
|
|
147
|
+
'Contact us for your next project.',
|
|
152
148
|
images: ['/images/og-contact.jpg'],
|
|
153
149
|
type: 'website',
|
|
154
150
|
},
|
|
@@ -66,11 +66,5 @@ export interface SiteSEOConfig {
|
|
|
66
66
|
*/
|
|
67
67
|
export interface SEOConfig {
|
|
68
68
|
site: SiteSEOConfig;
|
|
69
|
-
pages:
|
|
70
|
-
home: PageSEOConfig;
|
|
71
|
-
about: PageSEOConfig;
|
|
72
|
-
works: PageSEOConfig;
|
|
73
|
-
blog: PageSEOConfig;
|
|
74
|
-
contact: PageSEOConfig;
|
|
75
|
-
};
|
|
69
|
+
pages: Record<string, PageSEOConfig>;
|
|
76
70
|
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getAdminClient } from "@/lib/supabase/server";
|
|
2
2
|
|
|
3
3
|
export class CategoryService {
|
|
4
4
|
private static table = "categories";
|
|
5
5
|
|
|
6
6
|
static async getAll() {
|
|
7
|
-
const supabase = await
|
|
7
|
+
const supabase = await getAdminClient();
|
|
8
8
|
const { data, error } = await supabase
|
|
9
9
|
.from(this.table)
|
|
10
10
|
.select("*")
|
|
@@ -20,7 +20,7 @@ export class CategoryService {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
static async getBySlug(slug: string) {
|
|
23
|
-
const supabase = await
|
|
23
|
+
const supabase = await getAdminClient();
|
|
24
24
|
const { data, error } = await supabase
|
|
25
25
|
.from(this.table)
|
|
26
26
|
.select("*")
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getAdminClient } from "@/lib/supabase/server";
|
|
2
2
|
|
|
3
3
|
export class ClientService {
|
|
4
4
|
private static table = "clients";
|
|
5
5
|
|
|
6
6
|
static async getAll() {
|
|
7
|
-
const supabase = await
|
|
7
|
+
const supabase = await getAdminClient();
|
|
8
8
|
const { data, error } = await supabase
|
|
9
9
|
.from(this.table)
|
|
10
10
|
.select("*")
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { getAdminClient } from "@/lib/supabase/server";
|
|
2
2
|
|
|
3
3
|
export class ProductService {
|
|
4
4
|
private static table = "products";
|
|
5
5
|
|
|
6
6
|
static async getAll() {
|
|
7
|
-
const supabase = await
|
|
7
|
+
const supabase = await getAdminClient();
|
|
8
8
|
const { data, error } = await supabase
|
|
9
9
|
.from(this.table)
|
|
10
10
|
.select("*, categories(title, slug)")
|
|
@@ -20,7 +20,7 @@ export class ProductService {
|
|
|
20
20
|
}
|
|
21
21
|
|
|
22
22
|
static async getBySlug(slug: string) {
|
|
23
|
-
const supabase = await
|
|
23
|
+
const supabase = await getAdminClient();
|
|
24
24
|
const { data, error } = await supabase
|
|
25
25
|
.from(this.table)
|
|
26
26
|
.select("*, categories(title, slug)")
|