diff --git a/app/billing/page.tsx b/app/billing/page.tsx
new file mode 100644
index 0000000..f98b413
--- /dev/null
+++ b/app/billing/page.tsx
@@ -0,0 +1,34 @@
+import { generateMetadata } from "@/src/lib/seo/metadata"
+import { breadcrumbListSchema } from "@/src/lib/seo/json-ld"
+
+export const metadata = generateMetadata({
+ title: "Billing",
+ description: "View billing history, manage payments, and track usage costs for your utility meters on EquipChain.",
+ path: "/billing",
+})
+
+export default function BillingPage() {
+ const jsonLd = breadcrumbListSchema([
+ { name: "Home", path: "/" },
+ { name: "Billing", path: "/billing" },
+ ])
+
+ return (
+ <>
+
+
+
+
+ Billing
+
+
+ Billing content coming soon.
+
+
+
+ >
+ )
+}
diff --git a/app/dashboard/page.tsx b/app/dashboard/page.tsx
new file mode 100644
index 0000000..efd85b4
--- /dev/null
+++ b/app/dashboard/page.tsx
@@ -0,0 +1,34 @@
+import { generateMetadata } from "@/src/lib/seo/metadata"
+import { breadcrumbListSchema } from "@/src/lib/seo/json-ld"
+
+export const metadata = generateMetadata({
+ title: "Dashboard",
+ description: "Overview of your utility meters, usage statistics, and recent activity on EquipChain.",
+ path: "/dashboard",
+})
+
+export default function DashboardPage() {
+ const jsonLd = breadcrumbListSchema([
+ { name: "Home", path: "/" },
+ { name: "Dashboard", path: "/dashboard" },
+ ])
+
+ return (
+ <>
+
+
+
+
+ Dashboard
+
+
+ Dashboard content coming soon.
+
+
+
+ >
+ )
+}
diff --git a/app/layout.tsx b/app/layout.tsx
index 62fd479..da1ab1b 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -2,6 +2,8 @@ import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
+const baseUrl = process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000";
+
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
@@ -13,8 +15,43 @@ const geistMono = Geist_Mono({
});
export const metadata: Metadata = {
- title: "Equipchain Dashboard",
- description: "Next.js frontend dashboard for Equipchain utility management on Stellar Soroban",
+ metadataBase: new URL(baseUrl),
+ title: {
+ default: "EquipChain Dashboard",
+ template: "%s | EquipChain",
+ },
+ description:
+ "Utility metering and billing dashboard for monitoring meters, managing gas buffers, and tracking usage on Stellar Soroban.",
+ openGraph: {
+ title: "EquipChain Dashboard",
+ description:
+ "Utility metering and billing dashboard for monitoring meters, managing gas buffers, and tracking usage on Stellar Soroban.",
+ url: baseUrl,
+ siteName: "EquipChain",
+ type: "website",
+ locale: "en_US",
+ images: [
+ {
+ url: `${baseUrl}/og-image.png`,
+ width: 1200,
+ height: 630,
+ alt: "EquipChain Dashboard",
+ },
+ ],
+ },
+ twitter: {
+ card: "summary_large_image",
+ site: "@equipchain",
+ creator: "@equipchain",
+ title: "EquipChain Dashboard",
+ description:
+ "Utility metering and billing dashboard for monitoring meters, managing gas buffers, and tracking usage on Stellar Soroban.",
+ images: [`${baseUrl}/og-image.png`],
+ },
+ robots: {
+ index: true,
+ follow: true,
+ },
};
export default function RootLayout({
diff --git a/app/meters/page.tsx b/app/meters/page.tsx
new file mode 100644
index 0000000..408e9cf
--- /dev/null
+++ b/app/meters/page.tsx
@@ -0,0 +1,34 @@
+import { generateMetadata } from "@/src/lib/seo/metadata"
+import { breadcrumbListSchema } from "@/src/lib/seo/json-ld"
+
+export const metadata = generateMetadata({
+ title: "Meters",
+ description: "View and manage your utility meters, monitor consumption, and configure meter parameters on EquipChain.",
+ path: "/meters",
+})
+
+export default function MetersPage() {
+ const jsonLd = breadcrumbListSchema([
+ { name: "Home", path: "/" },
+ { name: "Meters", path: "/meters" },
+ ])
+
+ return (
+ <>
+
+
+
+
+ Meters
+
+
+ Meters content coming soon.
+
+
+
+ >
+ )
+}
diff --git a/app/page.tsx b/app/page.tsx
index 7c06070..a9bc8cc 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,34 +1,56 @@
+import { generateMetadata } from "@/src/lib/seo/metadata"
+import { breadcrumbListSchema, webApplicationSchema, organizationSchema } from "@/src/lib/seo/json-ld"
+
+export const metadata = generateMetadata({
+ title: "Home",
+ description:
+ "Utility metering and billing dashboard for monitoring meters, managing gas buffers, and tracking usage on Stellar Soroban.",
+ path: "/",
+})
+
export default function Home() {
+ const jsonLd = {
+ ...webApplicationSchema(),
+ ...organizationSchema(),
+ ...breadcrumbListSchema([{ name: "Home", path: "/" }]),
+ }
+
return (
-
-
-
-
- Equipchain Dashboard
-
-
- Utility metering and billing dashboard for monitoring meters, managing gas buffers, and tracking usage on Stellar Soroban.
-
-
-
-
-
+ <>
+
+
+
+
+
+ Equipchain Dashboard
+
+
+ Utility metering and billing dashboard for monitoring meters, managing gas buffers, and tracking usage on Stellar Soroban.
+
+
+
+
+
+ >
);
}
diff --git a/app/robots.ts b/app/robots.ts
new file mode 100644
index 0000000..5228a34
--- /dev/null
+++ b/app/robots.ts
@@ -0,0 +1,26 @@
+import type { MetadataRoute } from "next"
+
+const baseUrl = process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000"
+const isProduction = process.env.NODE_ENV === "production"
+
+export default function robots(): MetadataRoute.Robots {
+ if (isProduction) {
+ return {
+ rules: [
+ {
+ userAgent: "*",
+ allow: "/",
+ disallow: ["/admin/", "/api/"],
+ },
+ ],
+ sitemap: `${baseUrl}/sitemap.xml`,
+ }
+ }
+
+ return {
+ rules: {
+ userAgent: "*",
+ disallow: "/",
+ },
+ }
+}
diff --git a/app/sitemap.ts b/app/sitemap.ts
new file mode 100644
index 0000000..0dd5760
--- /dev/null
+++ b/app/sitemap.ts
@@ -0,0 +1,38 @@
+import type { MetadataRoute } from "next"
+
+const baseUrl = process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000"
+
+export default function sitemap(): MetadataRoute.Sitemap {
+ return [
+ {
+ url: baseUrl,
+ lastModified: new Date(),
+ changeFrequency: "monthly",
+ priority: 1,
+ },
+ {
+ url: `${baseUrl}/dashboard`,
+ lastModified: new Date(),
+ changeFrequency: "monthly",
+ priority: 0.9,
+ },
+ {
+ url: `${baseUrl}/meters`,
+ lastModified: new Date(),
+ changeFrequency: "weekly",
+ priority: 0.8,
+ },
+ {
+ url: `${baseUrl}/billing`,
+ lastModified: new Date(),
+ changeFrequency: "weekly",
+ priority: 0.7,
+ },
+ {
+ url: `${baseUrl}/streams`,
+ lastModified: new Date(),
+ changeFrequency: "weekly",
+ priority: 0.6,
+ },
+ ]
+}
diff --git a/app/streams/page.tsx b/app/streams/page.tsx
new file mode 100644
index 0000000..2ef9e92
--- /dev/null
+++ b/app/streams/page.tsx
@@ -0,0 +1,34 @@
+import { generateMetadata } from "@/src/lib/seo/metadata"
+import { breadcrumbListSchema } from "@/src/lib/seo/json-ld"
+
+export const metadata = generateMetadata({
+ title: "Streams",
+ description: "Monitor real-time data streams from your utility meters and view live usage feeds on EquipChain.",
+ path: "/streams",
+})
+
+export default function StreamsPage() {
+ const jsonLd = breadcrumbListSchema([
+ { name: "Home", path: "/" },
+ { name: "Streams", path: "/streams" },
+ ])
+
+ return (
+ <>
+
+
+
+
+ Streams
+
+
+ Streams content coming soon.
+
+
+
+ >
+ )
+}
diff --git a/public/og-image.png b/public/og-image.png
new file mode 100644
index 0000000..28b5f82
Binary files /dev/null and b/public/og-image.png differ
diff --git a/src/lib/seo/json-ld.ts b/src/lib/seo/json-ld.ts
new file mode 100644
index 0000000..f8f5edf
--- /dev/null
+++ b/src/lib/seo/json-ld.ts
@@ -0,0 +1,50 @@
+const baseUrl =
+ process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000"
+
+export function webApplicationSchema() {
+ return {
+ "@context": "https://schema.org",
+ "@type": "WebApplication",
+ name: "EquipChain Dashboard",
+ url: baseUrl,
+ applicationCategory: "BusinessApplication",
+ operatingSystem: "Web",
+ description:
+ "Utility metering and billing dashboard for monitoring meters, managing gas buffers, and tracking usage on Stellar Soroban.",
+ browserRequirements: "Requires modern browser with JavaScript enabled",
+ }
+}
+
+export function organizationSchema() {
+ return {
+ "@context": "https://schema.org",
+ "@type": "Organization",
+ name: "EquipChain",
+ url: baseUrl,
+ description:
+ "Decentralized utility metering and billing platform on Stellar Soroban",
+ contactPoint: {
+ "@type": "ContactPoint",
+ contactType: "support",
+ url: `${baseUrl}/contact`,
+ },
+ sameAs: [
+ "https://github.com/EquipChain",
+ "https://github.com/EquipChain/EquipChain-contracts",
+ "https://github.com/EquipChain/EquipChain-backend",
+ ],
+ }
+}
+
+export function breadcrumbListSchema(items: { name: string; path: string }[]) {
+ return {
+ "@context": "https://schema.org",
+ "@type": "BreadcrumbList",
+ itemListElement: items.map((item, index) => ({
+ "@type": "ListItem",
+ position: index + 1,
+ name: item.name,
+ item: `${baseUrl}${item.path}`,
+ })),
+ }
+}
diff --git a/src/lib/seo/metadata.ts b/src/lib/seo/metadata.ts
new file mode 100644
index 0000000..7e55074
--- /dev/null
+++ b/src/lib/seo/metadata.ts
@@ -0,0 +1,67 @@
+import type { Metadata } from "next"
+
+const baseUrl = process.env.NEXT_PUBLIC_BASE_URL ?? "http://localhost:3000"
+
+const titleTemplate = "%s | EquipChain"
+
+export const defaultMetadata: Metadata = {
+ metadataBase: new URL(baseUrl),
+ title: {
+ default: "EquipChain Dashboard",
+ template: titleTemplate,
+ },
+ description:
+ "Utility metering and billing dashboard for monitoring meters, managing gas buffers, and tracking usage on Stellar Soroban.",
+ openGraph: {
+ type: "website",
+ siteName: "EquipChain",
+ title: "EquipChain Dashboard",
+ description:
+ "Utility metering and billing dashboard for monitoring meters, managing gas buffers, and tracking usage on Stellar Soroban.",
+ url: baseUrl,
+ locale: "en_US",
+ },
+ twitter: {
+ card: "summary_large_image",
+ site: "@equipchain",
+ creator: "@equipchain",
+ },
+ robots: {
+ index: true,
+ follow: true,
+ },
+}
+
+export function generateMetadata({
+ title,
+ description,
+ path,
+ image,
+}: {
+ title: string
+ description?: string
+ path?: string
+ image?: string
+}): Metadata {
+ const url = path ? `${baseUrl}${path}` : baseUrl
+ const images = image
+ ? [{ url: image, width: 1200, height: 630, alt: title }]
+ : undefined
+
+ return {
+ title,
+ description,
+ alternates: { canonical: url },
+ openGraph: {
+ title,
+ description,
+ url,
+ images,
+ },
+ twitter: {
+ title,
+ description,
+ images: images ? images.map((i) => i.url) : undefined,
+ },
+ }
+}