Ship enterprise CRM frontend with real API wiring and thin app routes.

Connect all CRM pages to the BFF and CRM service, add module docs, and mark CRM available in the SuperApp catalog.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Mortezakoohjani 2026-07-26 20:58:17 +03:30
parent 065c053c16
commit e140908034
176 changed files with 6425 additions and 1 deletions

125
frontend/.eslintrc.json Normal file
View File

@ -0,0 +1,125 @@
{
"extends": "next/core-web-vitals",
"overrides": [
{
"files": ["app/accounting/**/*", "components/accounting/**/*"],
"rules": {
"no-restricted-imports": [
"error",
{
"patterns": [
{
"group": [
"@/components/beauty/**",
"@/components/healthcare/**",
"@/modules/beauty/**",
"@/modules/healthcare/**"
],
"message": "Accounting must not import beauty or healthcare modules."
}
]
}
]
}
},
{
"files": ["app/beauty/**/*", "modules/beauty/**/*"],
"rules": {
"no-restricted-imports": [
"error",
{
"patterns": [
{
"group": [
"@/components/accounting/**",
"@/components/healthcare/**",
"@/modules/accounting/**",
"@/modules/healthcare/**"
],
"message": "Beauty must not import accounting or healthcare modules."
}
]
}
]
}
},
{
"files": ["app/healthcare/**/*", "src/modules/healthcare/**/*"],
"rules": {
"no-restricted-imports": [
"error",
{
"patterns": [
{
"group": [
"@/components/accounting/**",
"@/components/beauty/**",
"@/modules/beauty/**",
"@/modules/accounting/**",
"@/src/modules/beauty/**"
],
"message": "Healthcare must not import accounting or beauty modules."
}
]
}
]
}
},
{
"files": ["app/crm/**/*", "modules/crm/**/*"],
"rules": {
"no-restricted-imports": [
"error",
{
"patterns": [
{
"group": [
"@/components/accounting/**",
"@/components/beauty/**",
"@/components/healthcare/**",
"@/modules/beauty/**",
"@/modules/healthcare/**",
"@/modules/accounting/**"
],
"message": "CRM must not import accounting, beauty, or healthcare modules."
}
]
}
]
}
},
{
"files": [
"components/ds/**/*",
"shared/**/*",
"components/providers/**/*",
"components/AuthGuard.tsx",
"components/AdminAuthGuard.tsx",
"components/ThemeProvider.tsx",
"components/SiteHeader.tsx",
"hooks/**/*"
],
"rules": {
"no-restricted-imports": [
"error",
{
"patterns": [
{
"group": [
"@/components/accounting/**",
"@/components/beauty/**",
"@/components/healthcare/**",
"@/modules/beauty/**",
"@/modules/healthcare/**",
"@/modules/accounting/**",
"@/modules/crm/**"
],
"message": "Shared/platform code must not import business module code."
}
]
}
]
}
}
]
}

View File

@ -0,0 +1,96 @@
import { NextRequest, NextResponse } from "next/server";
/**
* Same-origin BFF proxy CRM service.
* Browser calls /api/crm/* so TLS/CORS/DNS issues
* do not block CRUD from the SuperApp origin.
*/
const UPSTREAM =
process.env.CRM_SERVICE_URL ||
process.env.NEXT_PUBLIC_CRM_API_URL ||
"http://localhost:8003";
async function proxy(req: NextRequest, pathSegments: string[]) {
const path = pathSegments.join("/");
const url = new URL(req.url);
const target = `${UPSTREAM.replace(/\/$/, "")}/${path}${url.search}`;
const headers = new Headers();
const pass = ["authorization", "content-type", "x-tenant-id", "accept"];
for (const h of pass) {
const v = req.headers.get(h);
if (v) headers.set(h, v);
}
let body: ArrayBuffer | undefined;
if (req.method !== "GET" && req.method !== "HEAD") {
body = await req.arrayBuffer();
}
try {
const res = await fetch(target, {
method: req.method,
headers,
body,
cache: "no-store",
});
const outHeaders = new Headers();
const ct = res.headers.get("content-type");
if (ct) outHeaders.set("content-type", ct);
return new NextResponse(await res.arrayBuffer(), {
status: res.status,
headers: outHeaders,
});
} catch (err) {
const detail = err instanceof Error ? err.message : "upstream_unreachable";
return NextResponse.json(
{
error: {
code: "crm_proxy_error",
message: `پروکسی CRM به ${UPSTREAM} وصل نشد — ${detail}`,
},
},
{ status: 502 }
);
}
}
export async function GET(
req: NextRequest,
ctx: { params: Promise<{ path: string[] }> }
) {
const { path } = await ctx.params;
return proxy(req, path);
}
export async function POST(
req: NextRequest,
ctx: { params: Promise<{ path: string[] }> }
) {
const { path } = await ctx.params;
return proxy(req, path);
}
export async function PATCH(
req: NextRequest,
ctx: { params: Promise<{ path: string[] }> }
) {
const { path } = await ctx.params;
return proxy(req, path);
}
export async function PUT(
req: NextRequest,
ctx: { params: Promise<{ path: string[] }> }
) {
const { path } = await ctx.params;
return proxy(req, path);
}
export async function DELETE(
req: NextRequest,
ctx: { params: Promise<{ path: string[] }> }
) {
const { path } = await ctx.params;
return proxy(req, path);
}

View File

@ -0,0 +1,7 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,5 @@
import { LoadingState } from "@/components/ds";
export default function Loading() {
return <LoadingState label="در حال بارگذاری CRM…" />;
}

View File

@ -0,0 +1,3 @@
"use client";
export { CrmHub as default } from "@/modules/crm/pages/hub";

View File

@ -0,0 +1,4 @@
/** CRM root — sub-routes supply portal shells. */
export default function CrmRootLayout({ children }: { children: React.ReactNode }) {
return children;
}

View File

@ -0,0 +1,13 @@
import Link from "next/link";
import { Button, EmptyState } from "@/components/ds";
export default function CrmNotFound() {
return (
<div className="flex min-h-[50vh] items-center justify-center p-6">
<EmptyState
title="صفحه CRM یافت نشد"
action={<Link href="/crm/hub"><Button>بازگشت به CRM</Button></Link>}
/>
</div>
);
}

View File

@ -0,0 +1,7 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,5 @@
import { LoadingState } from "@/components/ds";
export default function Loading() {
return <LoadingState label="در حال بارگذاری CRM…" />;
}

View File

@ -0,0 +1,3 @@
"use client";
export { default } from "@/modules/crm/features/sales/activities";

View File

@ -0,0 +1,7 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,5 @@
import { LoadingState } from "@/components/ds";
export default function Loading() {
return <LoadingState label="در حال بارگذاری CRM…" />;
}

View File

@ -0,0 +1,3 @@
"use client";
export { default } from "@/modules/crm/features/sales/analytics";

View File

@ -0,0 +1,7 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,5 @@
import { LoadingState } from "@/components/ds";
export default function Loading() {
return <LoadingState label="در حال بارگذاری CRM…" />;
}

View File

@ -0,0 +1,3 @@
"use client";
export { ApiKeysPage as default } from "@/modules/crm/features/sales/scoped";

View File

@ -0,0 +1,7 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,5 @@
import { LoadingState } from "@/components/ds";
export default function Loading() {
return <LoadingState label="در حال بارگذاری CRM…" />;
}

View File

@ -0,0 +1,3 @@
"use client";
export { AuditLogsPage as default } from "@/modules/crm/features/sales/admin";

View File

@ -0,0 +1,7 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,5 @@
import { LoadingState } from "@/components/ds";
export default function Loading() {
return <LoadingState label="در حال بارگذاری CRM…" />;
}

View File

@ -0,0 +1,3 @@
"use client";
export { AutomationPage as default } from "@/modules/crm/features/sales/scoped";

View File

@ -0,0 +1,7 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,5 @@
import { LoadingState } from "@/components/ds";
export default function Loading() {
return <LoadingState label="در حال بارگذاری CRM…" />;
}

View File

@ -0,0 +1,3 @@
"use client";
export { default } from "@/modules/crm/features/sales/calls";

View File

@ -0,0 +1,7 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,5 @@
import { LoadingState } from "@/components/ds";
export default function Loading() {
return <LoadingState label="در حال بارگذاری CRM…" />;
}

View File

@ -0,0 +1,3 @@
"use client";
export { CampaignsPage as default } from "@/modules/crm/features/sales/scoped";

View File

@ -0,0 +1,7 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,5 @@
import { LoadingState } from "@/components/ds";
export default function Loading() {
return <LoadingState label="در حال بارگذاری CRM…" />;
}

View File

@ -0,0 +1,3 @@
"use client";
export { CapabilitiesPage as default } from "@/modules/crm/features/sales/admin";

View File

@ -0,0 +1,7 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,5 @@
import { LoadingState } from "@/components/ds";
export default function Loading() {
return <LoadingState label="در حال بارگذاری CRM…" />;
}

View File

@ -0,0 +1,3 @@
"use client";
export { default } from "@/modules/crm/features/sales/companies";

View File

@ -0,0 +1,7 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,5 @@
import { LoadingState } from "@/components/ds";
export default function Loading() {
return <LoadingState label="در حال بارگذاری CRM…" />;
}

View File

@ -0,0 +1,3 @@
"use client";
export { default } from "@/modules/crm/features/sales/contacts";

View File

@ -0,0 +1,7 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,5 @@
import { LoadingState } from "@/components/ds";
export default function Loading() {
return <LoadingState label="در حال بارگذاری CRM…" />;
}

View File

@ -0,0 +1,3 @@
"use client";
export { default } from "@/modules/crm/features/sales/custom-fields";

View File

@ -0,0 +1,7 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,5 @@
import { LoadingState } from "@/components/ds";
export default function Loading() {
return <LoadingState label="در حال بارگذاری CRM…" />;
}

View File

@ -0,0 +1,3 @@
"use client";
export { default } from "@/modules/crm/features/sales/customers";

View File

@ -0,0 +1,7 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,5 @@
import { LoadingState } from "@/components/ds";
export default function Loading() {
return <LoadingState label="در حال بارگذاری CRM…" />;
}

View File

@ -0,0 +1,3 @@
"use client";
export { ActivityDashboard as default } from "@/modules/crm/features/sales/dashboards";

View File

@ -0,0 +1,7 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,5 @@
import { LoadingState } from "@/components/ds";
export default function Loading() {
return <LoadingState label="در حال بارگذاری CRM…" />;
}

View File

@ -0,0 +1,3 @@
"use client";
export { CalendarDashboard as default } from "@/modules/crm/features/sales/dashboards";

View File

@ -0,0 +1,7 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,5 @@
import { LoadingState } from "@/components/ds";
export default function Loading() {
return <LoadingState label="در حال بارگذاری CRM…" />;
}

View File

@ -0,0 +1,3 @@
"use client";
export { LeadDashboard as default } from "@/modules/crm/features/sales/dashboards";

View File

@ -0,0 +1,7 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,5 @@
import { LoadingState } from "@/components/ds";
export default function Loading() {
return <LoadingState label="در حال بارگذاری CRM…" />;
}

View File

@ -0,0 +1,3 @@
"use client";
export { OverviewDashboard as default } from "@/modules/crm/features/sales/dashboards";

View File

@ -0,0 +1,7 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,5 @@
import { LoadingState } from "@/components/ds";
export default function Loading() {
return <LoadingState label="در حال بارگذاری CRM…" />;
}

View File

@ -0,0 +1,3 @@
"use client";
export { PipelineDashboard as default } from "@/modules/crm/features/sales/dashboards";

View File

@ -0,0 +1,7 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,5 @@
import { LoadingState } from "@/components/ds";
export default function Loading() {
return <LoadingState label="در حال بارگذاری CRM…" />;
}

View File

@ -0,0 +1,3 @@
"use client";
export { SalesDashboard as default } from "@/modules/crm/features/sales/dashboards";

View File

@ -0,0 +1,7 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,5 @@
import { LoadingState } from "@/components/ds";
export default function Loading() {
return <LoadingState label="در حال بارگذاری CRM…" />;
}

View File

@ -0,0 +1,3 @@
"use client";
export { default } from "@/modules/crm/features/sales/deals";

View File

@ -0,0 +1,7 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,5 @@
import { LoadingState } from "@/components/ds";
export default function Loading() {
return <LoadingState label="در حال بارگذاری CRM…" />;
}

View File

@ -0,0 +1,3 @@
"use client";
export { default } from "@/modules/crm/features/sales/documents";

View File

@ -0,0 +1,7 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,5 @@
import { LoadingState } from "@/components/ds";
export default function Loading() {
return <LoadingState label="در حال بارگذاری CRM…" />;
}

View File

@ -0,0 +1,3 @@
"use client";
export { default } from "@/modules/crm/features/sales/emails";

View File

@ -0,0 +1,7 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,5 @@
import { LoadingState } from "@/components/ds";
export default function Loading() {
return <LoadingState label="در حال بارگذاری CRM…" />;
}

View File

@ -0,0 +1,3 @@
"use client";
export { default } from "@/modules/crm/features/sales/export";

View File

@ -0,0 +1,7 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,5 @@
import { LoadingState } from "@/components/ds";
export default function Loading() {
return <LoadingState label="در حال بارگذاری CRM…" />;
}

View File

@ -0,0 +1,3 @@
"use client";
export { HealthPage as default } from "@/modules/crm/features/sales/admin";

View File

@ -0,0 +1,7 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,5 @@
import { LoadingState } from "@/components/ds";
export default function Loading() {
return <LoadingState label="در حال بارگذاری CRM…" />;
}

View File

@ -0,0 +1,3 @@
"use client";
export { ImportPage as default } from "@/modules/crm/features/sales/scoped";

View File

@ -0,0 +1,7 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,5 @@
import { LoadingState } from "@/components/ds";
export default function Loading() {
return <LoadingState label="در حال بارگذاری CRM…" />;
}

View File

@ -0,0 +1,3 @@
"use client";
export { IntegrationsPage as default } from "@/modules/crm/features/sales/scoped";

View File

@ -0,0 +1,7 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,5 @@
import { LoadingState } from "@/components/ds";
export default function Loading() {
return <LoadingState label="در حال بارگذاری CRM…" />;
}

View File

@ -0,0 +1,3 @@
"use client";
export { default } from "@/modules/crm/features/sales/kanban";

View File

@ -0,0 +1,5 @@
"use client";
import { createPortalLayout } from "@/modules/crm/components/createPortalLayout";
export default createPortalLayout("sales");

View File

@ -0,0 +1,7 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,5 @@
import { LoadingState } from "@/components/ds";
export default function Loading() {
return <LoadingState label="در حال بارگذاری CRM…" />;
}

View File

@ -0,0 +1,3 @@
"use client";
export { default } from "@/modules/crm/features/sales/leads";

View File

@ -0,0 +1,7 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,5 @@
import { LoadingState } from "@/components/ds";
export default function Loading() {
return <LoadingState label="در حال بارگذاری CRM…" />;
}

View File

@ -0,0 +1,3 @@
"use client";
export { default } from "@/modules/crm/features/sales/meetings";

View File

@ -0,0 +1,7 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,5 @@
import { LoadingState } from "@/components/ds";
export default function Loading() {
return <LoadingState label="در حال بارگذاری CRM…" />;
}

View File

@ -0,0 +1,3 @@
"use client";
export { default } from "@/modules/crm/features/sales/notes";

View File

@ -0,0 +1,7 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,5 @@
import { LoadingState } from "@/components/ds";
export default function Loading() {
return <LoadingState label="در حال بارگذاری CRM…" />;
}

View File

@ -0,0 +1,3 @@
"use client";
export { NotificationsPage as default } from "@/modules/crm/features/sales/admin";

View File

@ -0,0 +1,7 @@
"use client";
import { ErrorState } from "@/components/ds";
export default function Error({ error, reset }: { error: Error; reset: () => void }) {
return <ErrorState message={error.message} onRetry={reset} />;
}

View File

@ -0,0 +1,5 @@
import { LoadingState } from "@/components/ds";
export default function Loading() {
return <LoadingState label="در حال بارگذاری CRM…" />;
}

Some files were not shown because too many files have changed in this diff Show More