Initial commit: dashboard

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-10-03 23:17:36 +03:30
co-authored by Cursor
commit 3db32212ad
177 changed files with 29988 additions and 0 deletions
+64
View File
@@ -0,0 +1,64 @@
import type { ReactNode } from "react";
/**
* Install session from the URL hash BEFORE React AuthProvider runs.
* Cross-subdomain localStorage is empty until this runs; without it,
* ProtectedRoute saw ?businessId= and bounced admins back to /businesses.
*/
const HANDOFF_BOOT = `
(function () {
try {
var raw = location.hash ? location.hash.replace(/^#/, "") : "";
if (!raw) return;
var p = new URLSearchParams(raw);
var access = p.get("access");
var refresh = p.get("refresh");
var businessId = p.get("businessId");
var clearManaged = p.get("clearManaged") === "1";
var next = p.get("next") || "/profile";
if (!access || !refresh) return;
localStorage.setItem("marketplace_access_token", access);
localStorage.setItem("marketplace_refresh_token", refresh);
var root = "localhost";
try {
// Best-effort shared cookie for *.localhost / prod root
document.cookie =
"marketplace_access_token=" +
encodeURIComponent(access) +
"; Path=/; Max-Age=1209600; SameSite=Lax; Domain=" +
root;
document.cookie =
"marketplace_refresh_token=" +
encodeURIComponent(refresh) +
"; Path=/; Max-Age=1209600; SameSite=Lax; Domain=" +
root;
} catch (e) {}
if (businessId) {
localStorage.setItem("marketplace_managed_business_id", businessId);
sessionStorage.setItem("marketplace_managed_business_id", businessId);
} else if (clearManaged) {
localStorage.removeItem("marketplace_managed_business_id");
sessionStorage.removeItem("marketplace_managed_business_id");
}
var target = next.charAt(0) === "/" ? next : "/" + next;
location.replace(target);
} catch (e) {}
})();
`;
export default function AuthHandoffLayout({
children,
}: {
children: ReactNode;
}) {
return (
<>
<script dangerouslySetInnerHTML={{ __html: HANDOFF_BOOT }} />
{children}
</>
);
}
+31
View File
@@ -0,0 +1,31 @@
"use client";
/**
* Fallback UI if the inline boot script did not redirect
* (e.g. missing hash). Prefer the layout script for session install.
*/
export default function AuthHandoffPage() {
return (
<div
style={{
minHeight: "100vh",
display: "grid",
placeItems: "center",
background: "#f6f6f6",
fontSize: 12,
letterSpacing: "0.2em",
textTransform: "uppercase",
color: "#8e8e8e",
padding: 24,
textAlign: "center",
lineHeight: 1.6,
}}
>
Opening business dashboard…
<br />
<span style={{ letterSpacing: "0.06em", textTransform: "none", marginTop: 12 }}>
If this page stays here, go back and click Manage business again.
</span>
</div>
);
}
+20
View File
@@ -0,0 +1,20 @@
"use client";
import { BusinessMeshkeeGate } from "@/components/business/BusinessPageChrome";
import AddBlogForm from "@/components/catalog/AddBlogForm";
import { en } from "@/lib/i18n/en";
export default function BusinessAddBlogPage() {
return (
<BusinessMeshkeeGate>
{(meshkeeId) => (
<AddBlogForm
meshkeeId={meshkeeId}
listHref="/blog"
title={en.blog.addTitle}
lead={en.blog.addLead}
/>
)}
</BusinessMeshkeeGate>
);
}
+35
View File
@@ -0,0 +1,35 @@
"use client";
import Link from "next/link";
import {
BusinessListHeader,
BusinessMeshkeeGate,
} from "@/components/business/BusinessPageChrome";
import BlogPanel from "@/components/catalog/BlogPanel";
import { en } from "@/lib/i18n/en";
import styles from "@/components/BusinessDetailsPage.module.css";
export default function BusinessBlogPage() {
return (
<BusinessMeshkeeGate>
{(meshkeeId) => (
<>
<BusinessListHeader
title={en.blog.title}
lead={en.blog.lead}
action={
<Link href="/blog/new" className={`outline-btn ${styles.actionBtn}`}>
{en.blog.add}
</Link>
}
/>
<BlogPanel
meshkeeId={meshkeeId}
emptyLabel={en.blog.empty}
addHref="/blog/new"
/>
</>
)}
</BusinessMeshkeeGate>
);
}
+27
View File
@@ -0,0 +1,27 @@
"use client";
import {
BusinessListHeader,
BusinessMeshkeeGate,
} from "@/components/business/BusinessPageChrome";
import GalleryPanel from "@/components/catalog/GalleryPanel";
import { en } from "@/lib/i18n/en";
export default function BusinessGalleryPage() {
return (
<BusinessMeshkeeGate>
{(meshkeeId) => (
<>
<BusinessListHeader
title={en.gallery.title}
lead={en.gallery.lead}
/>
<GalleryPanel
meshkeeId={meshkeeId}
emptyLabel={en.gallery.empty}
/>
</>
)}
</BusinessMeshkeeGate>
);
}
+14
View File
@@ -0,0 +1,14 @@
import { ProtectedRoute } from "@/components/AuthGuards";
import BusinessShell from "@/components/BusinessShell";
export default function BusinessLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<ProtectedRoute requiredApp="business">
<BusinessShell>{children}</BusinessShell>
</ProtectedRoute>
);
}
+5
View File
@@ -0,0 +1,5 @@
import { redirect } from "next/navigation";
export default function BusinessIndexPage() {
redirect("/profile");
}
+20
View File
@@ -0,0 +1,20 @@
"use client";
import { BusinessMeshkeeGate } from "@/components/business/BusinessPageChrome";
import AddPortfolioForm from "@/components/catalog/AddPortfolioForm";
import { en } from "@/lib/i18n/en";
export default function BusinessAddPortfolioPage() {
return (
<BusinessMeshkeeGate>
{(meshkeeId) => (
<AddPortfolioForm
meshkeeId={meshkeeId}
listHref="/portfolios"
title={en.portfolios.addTitle}
lead={en.portfolios.addLead}
/>
)}
</BusinessMeshkeeGate>
);
}
+38
View File
@@ -0,0 +1,38 @@
"use client";
import Link from "next/link";
import {
BusinessListHeader,
BusinessMeshkeeGate,
} from "@/components/business/BusinessPageChrome";
import PortfoliosPanel from "@/components/catalog/PortfoliosPanel";
import { en } from "@/lib/i18n/en";
import styles from "@/components/BusinessDetailsPage.module.css";
export default function BusinessPortfoliosPage() {
return (
<BusinessMeshkeeGate>
{(meshkeeId) => (
<>
<BusinessListHeader
title={en.portfolios.title}
lead={en.portfolios.lead}
action={
<Link
href="/portfolios/new"
className={`outline-btn ${styles.actionBtn}`}
>
{en.portfolios.add}
</Link>
}
/>
<PortfoliosPanel
meshkeeId={meshkeeId}
emptyLabel={en.portfolios.empty}
addHref="/portfolios/new"
/>
</>
)}
</BusinessMeshkeeGate>
);
}
+24
View File
@@ -0,0 +1,24 @@
"use client";
import {
BusinessMeshkeeGate,
} from "@/components/business/BusinessPageChrome";
import ManageCategoriesPage from "@/components/catalog/ManageCategoriesPage";
import { en } from "@/lib/i18n/en";
export default function BusinessProductCategoriesPage() {
return (
<BusinessMeshkeeGate>
{(meshkeeId) => (
<ManageCategoriesPage
meshkeeId={meshkeeId}
entityType="product"
title={en.products.categoriesTitle}
lead={en.products.categoriesLead}
listHref="/products"
listLabel={en.products.title}
/>
)}
</BusinessMeshkeeGate>
);
}
+22
View File
@@ -0,0 +1,22 @@
"use client";
import {
BusinessMeshkeeGate,
} from "@/components/business/BusinessPageChrome";
import AddProductForm from "@/components/catalog/AddProductForm";
import { en } from "@/lib/i18n/en";
export default function BusinessAddProductPage() {
return (
<BusinessMeshkeeGate>
{(meshkeeId) => (
<AddProductForm
meshkeeId={meshkeeId}
listHref="/products"
title={en.products.addTitle}
lead={en.products.addLead}
/>
)}
</BusinessMeshkeeGate>
);
}
+46
View File
@@ -0,0 +1,46 @@
"use client";
import Link from "next/link";
import {
BusinessListHeader,
BusinessMeshkeeGate,
} from "@/components/business/BusinessPageChrome";
import ProductsPanel from "@/components/catalog/ProductsPanel";
import { en } from "@/lib/i18n/en";
import styles from "@/components/BusinessDetailsPage.module.css";
export default function BusinessProductsPage() {
return (
<BusinessMeshkeeGate>
{(meshkeeId) => (
<>
<BusinessListHeader
title={en.products.title}
lead={en.products.lead}
action={
<>
<Link
href="/products/categories"
className={`outline-btn ${styles.actionBtn}`}
>
{en.products.manageCategories}
</Link>
<Link
href="/products/new"
className={`outline-btn ${styles.actionBtn}`}
>
{en.products.add}
</Link>
</>
}
/>
<ProductsPanel
meshkeeId={meshkeeId}
emptyLabel={en.products.empty}
addHref="/products/new"
/>
</>
)}
</BusinessMeshkeeGate>
);
}
+14
View File
@@ -0,0 +1,14 @@
"use client";
import { BusinessMeshkeeGate } from "@/components/business/BusinessPageChrome";
import EditBusinessPage from "@/components/EditBusinessPage";
export default function BusinessProfilePage() {
return (
<BusinessMeshkeeGate>
{(meshkeeId) => (
<EditBusinessPage meshkeeBusinessId={meshkeeId} variant="business" />
)}
</BusinessMeshkeeGate>
);
}
+24
View File
@@ -0,0 +1,24 @@
import type { Metadata } from "next";
import { ProtectedRoute } from "@/components/AuthGuards";
import DashboardShell from "@/components/DashboardShell";
import EditBusinessPage from "@/components/EditBusinessPage";
export const metadata: Metadata = {
title: "Edit business | WilaSpace Dashboard",
};
export default async function EditBusinessRoute({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
return (
<ProtectedRoute requiredApp="admin">
<DashboardShell>
<EditBusinessPage listingId={id} />
</DashboardShell>
</ProtectedRoute>
);
}
+24
View File
@@ -0,0 +1,24 @@
import type { Metadata } from "next";
import { ProtectedRoute } from "@/components/AuthGuards";
import BusinessDetailsPage from "@/components/BusinessDetailsPage";
import DashboardShell from "@/components/DashboardShell";
export const metadata: Metadata = {
title: "Business details | WilaSpace Dashboard",
};
export default async function BusinessDetailsRoute({
params,
}: {
params: Promise<{ id: string }>;
}) {
const { id } = await params;
return (
<ProtectedRoute requiredApp="admin">
<DashboardShell>
<BusinessDetailsPage listingId={id} />
</DashboardShell>
</ProtectedRoute>
);
}
+18
View File
@@ -0,0 +1,18 @@
import type { Metadata } from "next";
import { ProtectedRoute } from "@/components/AuthGuards";
import BusinessCategoriesPage from "@/components/BusinessCategoriesPage";
import DashboardShell from "@/components/DashboardShell";
export const metadata: Metadata = {
title: "Business categories | IranBay Dashboard",
};
export default function BusinessCategoriesRoute() {
return (
<ProtectedRoute requiredApp="admin">
<DashboardShell>
<BusinessCategoriesPage />
</DashboardShell>
</ProtectedRoute>
);
}
+37
View File
@@ -0,0 +1,37 @@
import type { Metadata } from "next";
import { Suspense } from "react";
import { ProtectedRoute } from "@/components/AuthGuards";
import BusinessesPage from "@/components/BusinessesPage";
import DashboardShell from "@/components/DashboardShell";
export const metadata: Metadata = {
title: "Businesses | IranBay Dashboard",
};
export default function BusinessesRoute() {
return (
<ProtectedRoute requiredApp="admin">
<DashboardShell>
<Suspense
fallback={
<div
style={{
minHeight: 220,
display: "grid",
placeItems: "center",
fontSize: 12,
letterSpacing: "0.2em",
textTransform: "uppercase",
color: "#8e8e8e",
}}
>
Loading businesses…
</div>
}
>
<BusinessesPage />
</Suspense>
</DashboardShell>
</ProtectedRoute>
);
}
+14
View File
@@ -0,0 +1,14 @@
import { ProtectedRoute } from "@/components/AuthGuards";
import CustomerShell from "@/components/CustomerShell";
export default function CustomerLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<ProtectedRoute requiredApp="customer">
<CustomerShell>{children}</CustomerShell>
</ProtectedRoute>
);
}
+10
View File
@@ -0,0 +1,10 @@
import type { Metadata } from "next";
import CustomerHome from "@/components/CustomerHome";
export const metadata: Metadata = {
title: "Home | WilaSpace Account",
};
export default function CustomerHomePage() {
return <CustomerHome />;
}
+10
View File
@@ -0,0 +1,10 @@
import type { Metadata } from "next";
import CustomerProfile from "@/components/CustomerProfile";
export const metadata: Metadata = {
title: "Profile | WilaSpace Account",
};
export default function CustomerProfilePage() {
return <CustomerProfile />;
}
+16
View File
@@ -0,0 +1,16 @@
/* Self-hosted Iran Yekan — public/fonts/iranyekan/ */
@font-face {
font-family: "IRANYekan";
src: url("/fonts/iranyekan/IRANYekanWebLight.ttf") format("truetype");
font-weight: 300;
font-style: normal;
font-display: swap;
}
@font-face {
font-family: "IRANYekan";
src: url("/fonts/iranyekan/IRANYekanWebRegular.ttf") format("truetype");
font-weight: 400;
font-style: normal;
font-display: swap;
}
+10
View File
@@ -0,0 +1,10 @@
import type { Metadata } from "next";
import ForgotPasswordForm from "@/components/ForgotPasswordForm";
export const metadata: Metadata = {
title: "Forgot Password | WilaSpace",
};
export default function ForgotPasswordPage() {
return <ForgotPasswordForm />;
}
+207
View File
@@ -0,0 +1,207 @@
:root {
--font-montserrat: "Montserrat", sans-serif;
--font-farsi: "IRANYekan", Tahoma, "Segoe UI", sans-serif;
--font-ui: var(--font-montserrat), var(--font-farsi);
--color-black: #000;
--color-white: #fff;
--color-bg: #ffffff;
--color-card: #eeeeee;
--color-muted: #4a4a4a;
--color-soft: #8e8e8e;
--color-body: #aaaaaa;
--color-dark: #1e1e1e;
--color-explore: #f9f9f9;
--color-footer: #070707;
--color-footer-text: #bababa;
--color-footer-line: #232323;
--page-pad: 40px;
--max-width: 1920px;
--container-width: 1080px;
--sidebar-width: 248px;
--page-title-pattern: radial-gradient(
rgba(0, 0, 0, 0.12) 0.75px,
transparent 0.85px
);
--page-title-pattern-size: 14px 14px;
}
* {
box-sizing: border-box;
padding: 0;
margin: 0;
}
html {
height: 100%;
scroll-behavior: smooth;
}
html,
body {
max-width: 100vw;
overflow-x: hidden;
}
body {
min-height: 100%;
color: var(--color-black);
background: var(--color-bg);
font-family: var(--font-ui);
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
img {
display: block;
max-width: 100%;
}
button,
a {
font-family: inherit;
color: inherit;
}
a {
text-decoration: none;
color: inherit;
}
button {
background: none;
border: none;
cursor: pointer;
}
.icon {
display: block;
object-fit: contain;
}
.outline-btn {
position: relative;
isolation: isolate;
display: inline-flex;
align-items: center;
justify-content: center;
border: 0.5px solid var(--color-black);
border-radius: 3px;
background: transparent;
color: var(--color-black);
font-weight: 400;
text-transform: uppercase;
letter-spacing: 0.2em;
white-space: nowrap;
transition: color 0.3s ease, border-color 0.3s ease;
}
.outline-btn:hover,
.outline-btn:focus-visible {
color: var(--color-white);
background: transparent;
}
.outline-btn:hover :is(span, strong, em, p, a, label, button),
.outline-btn:focus-visible :is(span, strong, em, p, a, label, button) {
color: var(--color-white);
}
.tile-hover,
.outline-btn {
position: relative;
isolation: isolate;
}
.tile-hover::before,
.outline-btn::before {
content: "";
position: absolute;
inset: 0;
background: var(--color-black);
clip-path: inset(100% 0 0 0);
transition: clip-path 0.45s cubic-bezier(0.25, 0.8, 0.25, 1);
pointer-events: none;
border-radius: inherit;
}
.tile-hover::before {
z-index: 0;
}
.outline-btn::before {
z-index: -1;
}
.tile-hover:hover::before,
.tile-hover:focus-within::before,
.outline-btn:hover::before,
.outline-btn:focus-visible::before {
clip-path: inset(0 0 0 0);
}
.tile-hover > *:not(img),
.outline-btn > * {
position: relative;
z-index: 2;
}
.tile-hover:hover,
.tile-hover:focus-within,
.tile-hover:hover :is(h1, h2, h3, p, span, strong, a),
.tile-hover:focus-within :is(h1, h2, h3, p, span, strong, a) {
color: var(--color-white);
}
.tile-hover :is(h1, h2, h3, p, span, strong, a) {
transition: color 0.3s ease, border-color 0.3s ease;
}
.outline-btn:hover img,
.outline-btn:focus-visible img {
position: relative;
z-index: 2;
}
@media (prefers-reduced-motion: reduce) {
.tile-hover::before,
.outline-btn::before {
transition: none;
}
}
.sr-only {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
clip: rect(0, 0, 0, 0);
white-space: nowrap;
border: 0;
}
.fa,
[lang="fa"],
:lang(fa) {
font-family: var(--font-farsi);
font-weight: 400;
letter-spacing: 0;
}
[dir="ltr"] {
unicode-bidi: isolate;
direction: ltr;
}
@media (max-width: 1100px) {
:root {
--page-pad: 28px;
}
}
@media (max-width: 720px) {
:root {
--page-pad: 20px;
}
}
+31
View File
@@ -0,0 +1,31 @@
import type { Metadata } from "next";
import { Montserrat } from "next/font/google";
import { AuthProvider } from "@/components/AuthProvider";
import "./fonts/iranyekan.css";
import "./globals.css";
const montserrat = Montserrat({
subsets: ["latin"],
weight: ["300", "400", "500", "600"],
variable: "--font-montserrat",
display: "swap",
});
export const metadata: Metadata = {
title: "WilaSpace Dashboard",
description: "Admin dashboard for WilaSpace marketplace gate.",
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en" dir="ltr" className={montserrat.variable}>
<body>
<AuthProvider>{children}</AuthProvider>
</body>
</html>
);
}
+35
View File
@@ -0,0 +1,35 @@
import type { Metadata } from "next";
import { Suspense } from "react";
import { GuestRoute } from "@/components/AuthGuards";
import LoginForm from "@/components/LoginForm";
export const metadata: Metadata = {
title: "Login | WilaSpace",
};
export default function LoginPage() {
return (
<GuestRoute>
<Suspense
fallback={
<div
style={{
minHeight: "100vh",
display: "grid",
placeItems: "center",
background: "#f6f6f6",
fontSize: 12,
letterSpacing: "0.2em",
textTransform: "uppercase",
color: "#8e8e8e",
}}
>
Loading
</div>
}
>
<LoginForm />
</Suspense>
</GuestRoute>
);
}
+20
View File
@@ -0,0 +1,20 @@
"use client";
import { useParams } from "next/navigation";
import AddBlogForm from "@/components/catalog/AddBlogForm";
import { en } from "@/lib/i18n/en";
export default function ManageAddBlogPage() {
const params = useParams<{ meshkeeId: string }>();
const meshkeeId = String(params.meshkeeId ?? "");
const listHref = `/manage/${encodeURIComponent(meshkeeId)}/blog`;
return (
<AddBlogForm
meshkeeId={meshkeeId}
listHref={listHref}
title={en.blog.addTitle}
lead={en.blog.addLead}
/>
);
}
+33
View File
@@ -0,0 +1,33 @@
"use client";
import Link from "next/link";
import { useParams } from "next/navigation";
import { BusinessListHeader } from "@/components/business/BusinessPageChrome";
import BlogPanel from "@/components/catalog/BlogPanel";
import { en } from "@/lib/i18n/en";
import styles from "@/components/BusinessDetailsPage.module.css";
export default function ManageBusinessBlogPage() {
const params = useParams<{ meshkeeId: string }>();
const meshkeeId = String(params.meshkeeId ?? "");
const addHref = `/manage/${encodeURIComponent(meshkeeId)}/blog/new`;
return (
<>
<BusinessListHeader
title={en.blog.title}
lead={en.blog.lead}
action={
<Link href={addHref} className={`outline-btn ${styles.actionBtn}`}>
{en.blog.add}
</Link>
}
/>
<BlogPanel
meshkeeId={meshkeeId}
emptyLabel={en.blog.empty}
addHref={addHref}
/>
</>
);
}
+18
View File
@@ -0,0 +1,18 @@
"use client";
import { useParams } from "next/navigation";
import { BusinessListHeader } from "@/components/business/BusinessPageChrome";
import GalleryPanel from "@/components/catalog/GalleryPanel";
import { en } from "@/lib/i18n/en";
export default function ManageBusinessGalleryPage() {
const params = useParams<{ meshkeeId: string }>();
const meshkeeId = String(params.meshkeeId ?? "");
return (
<>
<BusinessListHeader title={en.gallery.title} lead={en.gallery.lead} />
<GalleryPanel meshkeeId={meshkeeId} emptyLabel={en.gallery.empty} />
</>
);
}
+53
View File
@@ -0,0 +1,53 @@
"use client";
import { useEffect } from "react";
import { useParams } from "next/navigation";
import { ProtectedRoute } from "@/components/AuthGuards";
import BusinessShell from "@/components/BusinessShell";
import { setManagedBusinessId } from "@/lib/managed-business";
import { useAuth } from "@/components/AuthProvider";
function ManageBusinessBootstrap({ children }: { children: React.ReactNode }) {
const params = useParams<{ meshkeeId: string }>();
const meshkeeId = String(params.meshkeeId ?? "");
const { isLoading } = useAuth();
useEffect(() => {
if (meshkeeId) setManagedBusinessId(meshkeeId);
}, [meshkeeId]);
if (isLoading) {
return (
<div
style={{
minHeight: "100vh",
display: "grid",
placeItems: "center",
background: "#f6f6f6",
fontSize: 12,
letterSpacing: "0.2em",
textTransform: "uppercase",
color: "#8e8e8e",
}}
>
Loading
</div>
);
}
return (
<BusinessShell manageMeshkeeId={meshkeeId}>{children}</BusinessShell>
);
}
export default function ManageBusinessLayout({
children,
}: {
children: React.ReactNode;
}) {
return (
<ProtectedRoute requiredApp="admin">
<ManageBusinessBootstrap>{children}</ManageBusinessBootstrap>
</ProtectedRoute>
);
}
+12
View File
@@ -0,0 +1,12 @@
"use client";
import { redirect } from "next/navigation";
export default async function ManageBusinessIndex({
params,
}: {
params: Promise<{ meshkeeId: string }>;
}) {
const { meshkeeId } = await params;
redirect(`/manage/${meshkeeId}/profile`);
}
@@ -0,0 +1,20 @@
"use client";
import { useParams } from "next/navigation";
import AddPortfolioForm from "@/components/catalog/AddPortfolioForm";
import { en } from "@/lib/i18n/en";
export default function ManageAddPortfolioPage() {
const params = useParams<{ meshkeeId: string }>();
const meshkeeId = String(params.meshkeeId ?? "");
const listHref = `/manage/${encodeURIComponent(meshkeeId)}/portfolios`;
return (
<AddPortfolioForm
meshkeeId={meshkeeId}
listHref={listHref}
title={en.portfolios.addTitle}
lead={en.portfolios.addLead}
/>
);
}
@@ -0,0 +1,33 @@
"use client";
import Link from "next/link";
import { useParams } from "next/navigation";
import { BusinessListHeader } from "@/components/business/BusinessPageChrome";
import PortfoliosPanel from "@/components/catalog/PortfoliosPanel";
import { en } from "@/lib/i18n/en";
import styles from "@/components/BusinessDetailsPage.module.css";
export default function ManageBusinessPortfoliosPage() {
const params = useParams<{ meshkeeId: string }>();
const meshkeeId = String(params.meshkeeId ?? "");
const addHref = `/manage/${encodeURIComponent(meshkeeId)}/portfolios/new`;
return (
<>
<BusinessListHeader
title={en.portfolios.title}
lead={en.portfolios.lead}
action={
<Link href={addHref} className={`outline-btn ${styles.actionBtn}`}>
{en.portfolios.add}
</Link>
}
/>
<PortfoliosPanel
meshkeeId={meshkeeId}
emptyLabel={en.portfolios.empty}
addHref={addHref}
/>
</>
);
}
@@ -0,0 +1,23 @@
"use client";
import { useParams } from "next/navigation";
import ManageCategoriesPage from "@/components/catalog/ManageCategoriesPage";
import { en } from "@/lib/i18n/en";
export default function ManageProductCategoriesPage() {
const params = useParams<{ meshkeeId: string }>();
const meshkeeId = String(params.meshkeeId ?? "");
const listHref = `/manage/${encodeURIComponent(meshkeeId)}/products`;
return (
<ManageCategoriesPage
meshkeeId={meshkeeId}
entityType="product"
title={en.products.categoriesTitle}
lead={en.products.categoriesLead}
homeHref={`/manage/${encodeURIComponent(meshkeeId)}`}
listHref={listHref}
listLabel={en.products.title}
/>
);
}
@@ -0,0 +1,20 @@
"use client";
import { useParams } from "next/navigation";
import AddProductForm from "@/components/catalog/AddProductForm";
import { en } from "@/lib/i18n/en";
export default function ManageAddProductPage() {
const params = useParams<{ meshkeeId: string }>();
const meshkeeId = String(params.meshkeeId ?? "");
const listHref = `/manage/${encodeURIComponent(meshkeeId)}/products`;
return (
<AddProductForm
meshkeeId={meshkeeId}
listHref={listHref}
title={en.products.addTitle}
lead={en.products.addLead}
/>
);
}
+41
View File
@@ -0,0 +1,41 @@
"use client";
import Link from "next/link";
import { useParams } from "next/navigation";
import { BusinessListHeader } from "@/components/business/BusinessPageChrome";
import ProductsPanel from "@/components/catalog/ProductsPanel";
import { en } from "@/lib/i18n/en";
import styles from "@/components/BusinessDetailsPage.module.css";
export default function ManageBusinessProductsPage() {
const params = useParams<{ meshkeeId: string }>();
const meshkeeId = String(params.meshkeeId ?? "");
const base = `/manage/${encodeURIComponent(meshkeeId)}/products`;
return (
<>
<BusinessListHeader
title={en.products.title}
lead={en.products.lead}
action={
<>
<Link
href={`${base}/categories`}
className={`outline-btn ${styles.actionBtn}`}
>
{en.products.manageCategories}
</Link>
<Link href={`${base}/new`} className={`outline-btn ${styles.actionBtn}`}>
{en.products.add}
</Link>
</>
}
/>
<ProductsPanel
meshkeeId={meshkeeId}
emptyLabel={en.products.empty}
addHref={`${base}/new`}
/>
</>
);
}
+13
View File
@@ -0,0 +1,13 @@
"use client";
import { useParams } from "next/navigation";
import EditBusinessPage from "@/components/EditBusinessPage";
export default function ManageBusinessProfilePage() {
const params = useParams<{ meshkeeId: string }>();
const meshkeeId = String(params.meshkeeId ?? "");
return (
<EditBusinessPage meshkeeBusinessId={meshkeeId} variant="business" />
);
}
+18
View File
@@ -0,0 +1,18 @@
import type { Metadata } from "next";
import { ProtectedRoute } from "@/components/AuthGuards";
import DashboardShell from "@/components/DashboardShell";
import HomeDashboard from "@/components/HomeDashboard";
export const metadata: Metadata = {
title: "Overview | WilaSpace Dashboard",
};
export default function DashboardHomePage() {
return (
<ProtectedRoute requiredApp="admin">
<DashboardShell>
<HomeDashboard />
</DashboardShell>
</ProtectedRoute>
);
}
+5
View File
@@ -0,0 +1,5 @@
import { redirect } from "next/navigation";
export default function WebsiteImagesRedirect() {
redirect("/website/images");
}
+18
View File
@@ -0,0 +1,18 @@
import type { Metadata } from "next";
import { ProtectedRoute } from "@/components/AuthGuards";
import BlogCategoriesPage from "@/components/BlogCategoriesPage";
import DashboardShell from "@/components/DashboardShell";
export const metadata: Metadata = {
title: "Blog categories | IranBay Dashboard",
};
export default function BlogCategoriesRoute() {
return (
<ProtectedRoute requiredApp="admin">
<DashboardShell>
<BlogCategoriesPage />
</DashboardShell>
</ProtectedRoute>
);
}
+18
View File
@@ -0,0 +1,18 @@
import type { Metadata } from "next";
import { ProtectedRoute } from "@/components/AuthGuards";
import DashboardShell from "@/components/DashboardShell";
import WebsiteBlogForm from "@/components/WebsiteBlogForm";
export const metadata: Metadata = {
title: "Add blog post | IranBay Dashboard",
};
export default function WebsiteBlogNewRoute() {
return (
<ProtectedRoute requiredApp="admin">
<DashboardShell>
<WebsiteBlogForm />
</DashboardShell>
</ProtectedRoute>
);
}
+18
View File
@@ -0,0 +1,18 @@
import type { Metadata } from "next";
import { ProtectedRoute } from "@/components/AuthGuards";
import DashboardShell from "@/components/DashboardShell";
import WebsiteBlogListPage from "@/components/WebsiteBlogListPage";
export const metadata: Metadata = {
title: "Blog | IranBay Dashboard",
};
export default function WebsiteBlogRoute() {
return (
<ProtectedRoute requiredApp="admin">
<DashboardShell>
<WebsiteBlogListPage />
</DashboardShell>
</ProtectedRoute>
);
}
+18
View File
@@ -0,0 +1,18 @@
import type { Metadata } from "next";
import { ProtectedRoute } from "@/components/AuthGuards";
import DashboardShell from "@/components/DashboardShell";
import SpecialBusinessesPage from "@/components/SpecialBusinessesPage";
export const metadata: Metadata = {
title: "Carousels | IranBay Dashboard",
};
export default function CarouselsRoute() {
return (
<ProtectedRoute requiredApp="admin">
<DashboardShell>
<SpecialBusinessesPage />
</DashboardShell>
</ProtectedRoute>
);
}
+18
View File
@@ -0,0 +1,18 @@
import type { Metadata } from "next";
import { ProtectedRoute } from "@/components/AuthGuards";
import DashboardShell from "@/components/DashboardShell";
import WebsiteImagesPage from "@/components/WebsiteImagesPage";
export const metadata: Metadata = {
title: "Website images | IranBay Dashboard",
};
export default function WebsiteImagesRoute() {
return (
<ProtectedRoute requiredApp="admin">
<DashboardShell>
<WebsiteImagesPage />
</DashboardShell>
</ProtectedRoute>
);
}
+5
View File
@@ -0,0 +1,5 @@
import { redirect } from "next/navigation";
export default function SpecialBusinessesRedirect() {
redirect("/website/carousels");
}