create-mercato-app 0.4.2-canary-e5804f7db1
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 +94 -0
- package/bin/create-mercato-app +21 -0
- package/dist/index.js +177 -0
- package/package.json +42 -0
- package/template/.env.example +217 -0
- package/template/.yarnrc.yml.template +2 -0
- package/template/components.json +22 -0
- package/template/gitignore +50 -0
- package/template/next.config.ts +28 -0
- package/template/package.json.template +87 -0
- package/template/postcss.config.mjs +5 -0
- package/template/public/catch-the-tornado-logo.png +0 -0
- package/template/public/file.svg +1 -0
- package/template/public/globe.svg +1 -0
- package/template/public/next.svg +1 -0
- package/template/public/open-mercato.svg +50 -0
- package/template/public/vercel.svg +1 -0
- package/template/public/window.svg +1 -0
- package/template/src/app/(backend)/backend/[...slug]/page.tsx +59 -0
- package/template/src/app/(backend)/backend/layout.tsx +350 -0
- package/template/src/app/(backend)/backend/page.tsx +13 -0
- package/template/src/app/(frontend)/[...slug]/page.tsx +32 -0
- package/template/src/app/api/[...slug]/route.ts +227 -0
- package/template/src/app/api/docs/markdown/route.ts +35 -0
- package/template/src/app/api/docs/openapi/route.ts +30 -0
- package/template/src/app/globals.css +178 -0
- package/template/src/app/layout.tsx +76 -0
- package/template/src/app/page.tsx +134 -0
- package/template/src/bootstrap.ts +58 -0
- package/template/src/components/ClientBootstrap.tsx +37 -0
- package/template/src/components/GlobalNoticeBars.tsx +116 -0
- package/template/src/components/OrganizationSwitcher.tsx +360 -0
- package/template/src/components/StartPageContent.tsx +269 -0
- package/template/src/components/ui/button.tsx +59 -0
- package/template/src/components/ui/card.tsx +92 -0
- package/template/src/components/ui/checkbox.tsx +29 -0
- package/template/src/components/ui/input.tsx +21 -0
- package/template/src/components/ui/label.tsx +24 -0
- package/template/src/di.ts +11 -0
- package/template/src/i18n/de.json +375 -0
- package/template/src/i18n/en.json +376 -0
- package/template/src/i18n/es.json +376 -0
- package/template/src/i18n/pl.json +375 -0
- package/template/src/modules/.gitkeep +0 -0
- package/template/src/modules.ts +31 -0
- package/template/src/proxy.ts +17 -0
- package/template/tsconfig.json +54 -0
- package/template/types/pg/index.d.ts +1 -0
- package/template/types/react-big-calendar/index.d.ts +16 -0
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{APP_NAME}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "mercato server dev",
|
|
7
|
+
"build": "next build",
|
|
8
|
+
"start": "NODE_OPTIONS='-r newrelic' mercato server start",
|
|
9
|
+
"lint": "next lint",
|
|
10
|
+
"typecheck": "tsc --noEmit",
|
|
11
|
+
"test": "jest --config jest.config.cjs",
|
|
12
|
+
"generate": "mercato generate",
|
|
13
|
+
"db:generate": "mercato db generate",
|
|
14
|
+
"db:migrate": "mercato db migrate",
|
|
15
|
+
"db:greenfield": "mercato db greenfield",
|
|
16
|
+
"initialize": "mercato init",
|
|
17
|
+
"reinstall": "mercato init --reinstall"
|
|
18
|
+
},
|
|
19
|
+
"dependencies": {
|
|
20
|
+
"@ai-sdk/openai": "^2.0.80",
|
|
21
|
+
"@mikro-orm/core": "^6.6.2",
|
|
22
|
+
"@mikro-orm/migrations": "^6.6.2",
|
|
23
|
+
"@mikro-orm/postgresql": "^6.6.2",
|
|
24
|
+
"@open-mercato/ai-assistant": "{{PACKAGE_VERSION}}",
|
|
25
|
+
"@open-mercato/cache": "{{PACKAGE_VERSION}}",
|
|
26
|
+
"@open-mercato/cli": "{{PACKAGE_VERSION}}",
|
|
27
|
+
"@open-mercato/content": "{{PACKAGE_VERSION}}",
|
|
28
|
+
"@open-mercato/core": "{{PACKAGE_VERSION}}",
|
|
29
|
+
"@open-mercato/events": "{{PACKAGE_VERSION}}",
|
|
30
|
+
"@open-mercato/onboarding": "{{PACKAGE_VERSION}}",
|
|
31
|
+
"@open-mercato/queue": "{{PACKAGE_VERSION}}",
|
|
32
|
+
"@open-mercato/search": "{{PACKAGE_VERSION}}",
|
|
33
|
+
"@open-mercato/shared": "{{PACKAGE_VERSION}}",
|
|
34
|
+
"@open-mercato/ui": "{{PACKAGE_VERSION}}",
|
|
35
|
+
"@radix-ui/react-checkbox": "^1.3.3",
|
|
36
|
+
"@radix-ui/react-dialog": "^1.0.5",
|
|
37
|
+
"@radix-ui/react-label": "^2.1.8",
|
|
38
|
+
"@radix-ui/react-slot": "^1.2.4",
|
|
39
|
+
"@react-email/components": "^1.0.1",
|
|
40
|
+
"@tanstack/react-query": "^5.90.12",
|
|
41
|
+
"@tanstack/react-table": "^8.20.5",
|
|
42
|
+
"@uiw/react-markdown-preview": "^5.1.5",
|
|
43
|
+
"@uiw/react-md-editor": "^4.0.11",
|
|
44
|
+
"ai": "^5.0.108",
|
|
45
|
+
"awilix": "^12.0.5",
|
|
46
|
+
"bcryptjs": "^3.0.3",
|
|
47
|
+
"class-variance-authority": "^0.7.1",
|
|
48
|
+
"clsx": "^2.1.1",
|
|
49
|
+
"dotenv": "^17.2.3",
|
|
50
|
+
"lucide-react": "^0.556.0",
|
|
51
|
+
"newrelic": "^13.7.0",
|
|
52
|
+
"next": "16.0.10",
|
|
53
|
+
"pdf2pic": "^3.2.0",
|
|
54
|
+
"pg": "^8.16.3",
|
|
55
|
+
"react": "19.2.1",
|
|
56
|
+
"react-big-calendar": "^1.19.4",
|
|
57
|
+
"react-dom": "19.2.1",
|
|
58
|
+
"react-email": "^5.0.6",
|
|
59
|
+
"remark-gfm": "^4.0.1",
|
|
60
|
+
"resend": "^6.5.2",
|
|
61
|
+
"semver": "^7.7.3",
|
|
62
|
+
"tailwind-merge": "^3.4.0",
|
|
63
|
+
"zod": "^4.1.13"
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@tailwindcss/postcss": "^4.1.17",
|
|
67
|
+
"@testing-library/dom": "^10.4.0",
|
|
68
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
69
|
+
"@testing-library/react": "^16.2.0",
|
|
70
|
+
"@types/jest": "^30.0.0",
|
|
71
|
+
"@types/node": "^24.10.1",
|
|
72
|
+
"@types/react": "^19.2.7",
|
|
73
|
+
"@types/react-dom": "^19.2.3",
|
|
74
|
+
"eslint": "^9.0.0",
|
|
75
|
+
"eslint-config-next": "16.0.7",
|
|
76
|
+
"jest": "^30.2.0",
|
|
77
|
+
"tailwindcss": "^4.1.17",
|
|
78
|
+
"ts-jest": "^29.4.6",
|
|
79
|
+
"tw-animate-css": "^1.4.0",
|
|
80
|
+
"typescript": "^5.9.3"
|
|
81
|
+
},
|
|
82
|
+
"optionalDependencies": {
|
|
83
|
+
"better-sqlite3": "^12.5.0",
|
|
84
|
+
"bullmq": "^5.34.8",
|
|
85
|
+
"ioredis": "^5.8.2"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" viewBox="0 0 16 16" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 13.5V5.41a1 1 0 0 0-.3-.7L9.8.29A1 1 0 0 0 9.08 0H1.5v13.5A2.5 2.5 0 0 0 4 16h8a2.5 2.5 0 0 0 2.5-2.5m-1.5 0v-7H8v-5H3v12a1 1 0 0 0 1 1h8a1 1 0 0 0 1-1M9.5 5V2.12L12.38 5zM5.13 5h-.62v1.25h2.12V5zm-.62 3h7.12v1.25H4.5zm.62 3h-.62v1.25h7.12V11z" clip-rule="evenodd" fill="#666" fill-rule="evenodd"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><g clip-path="url(#a)"><path fill-rule="evenodd" clip-rule="evenodd" d="M10.27 14.1a6.5 6.5 0 0 0 3.67-3.45q-1.24.21-2.7.34-.31 1.83-.97 3.1M8 16A8 8 0 1 0 8 0a8 8 0 0 0 0 16m.48-1.52a7 7 0 0 1-.96 0H7.5a4 4 0 0 1-.84-1.32q-.38-.89-.63-2.08a40 40 0 0 0 3.92 0q-.25 1.2-.63 2.08a4 4 0 0 1-.84 1.31zm2.94-4.76q1.66-.15 2.95-.43a7 7 0 0 0 0-2.58q-1.3-.27-2.95-.43a18 18 0 0 1 0 3.44m-1.27-3.54a17 17 0 0 1 0 3.64 39 39 0 0 1-4.3 0 17 17 0 0 1 0-3.64 39 39 0 0 1 4.3 0m1.1-1.17q1.45.13 2.69.34a6.5 6.5 0 0 0-3.67-3.44q.65 1.26.98 3.1M8.48 1.5l.01.02q.41.37.84 1.31.38.89.63 2.08a40 40 0 0 0-3.92 0q.25-1.2.63-2.08a4 4 0 0 1 .85-1.32 7 7 0 0 1 .96 0m-2.75.4a6.5 6.5 0 0 0-3.67 3.44 29 29 0 0 1 2.7-.34q.31-1.83.97-3.1M4.58 6.28q-1.66.16-2.95.43a7 7 0 0 0 0 2.58q1.3.27 2.95.43a18 18 0 0 1 0-3.44m.17 4.71q-1.45-.12-2.69-.34a6.5 6.5 0 0 0 3.67 3.44q-.65-1.27-.98-3.1" fill="#666"/></g><defs><clipPath id="a"><path fill="#fff" d="M0 0h16v16H0z"/></clipPath></defs></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 394 80"><path fill="#000" d="M262 0h68.5v12.7h-27.2v66.6h-13.6V12.7H262V0ZM149 0v12.7H94v20.4h44.3v12.6H94v21h55v12.6H80.5V0h68.7zm34.3 0h-17.8l63.8 79.4h17.9l-32-39.7 32-39.6h-17.9l-23 28.6-23-28.6zm18.3 56.7-9-11-27.1 33.7h17.8l18.3-22.7z"/><path fill="#000" d="M81 79.3 17 0H0v79.3h13.6V17l50.2 62.3H81Zm252.6-.4c-1 0-1.8-.4-2.5-1s-1.1-1.6-1.1-2.6.3-1.8 1-2.5 1.6-1 2.6-1 1.8.3 2.5 1a3.4 3.4 0 0 1 .6 4.3 3.7 3.7 0 0 1-3 1.8zm23.2-33.5h6v23.3c0 2.1-.4 4-1.3 5.5a9.1 9.1 0 0 1-3.8 3.5c-1.6.8-3.5 1.3-5.7 1.3-2 0-3.7-.4-5.3-1s-2.8-1.8-3.7-3.2c-.9-1.3-1.4-3-1.4-5h6c.1.8.3 1.6.7 2.2s1 1.2 1.6 1.5c.7.4 1.5.5 2.4.5 1 0 1.8-.2 2.4-.6a4 4 0 0 0 1.6-1.8c.3-.8.5-1.8.5-3V45.5zm30.9 9.1a4.4 4.4 0 0 0-2-3.3 7.5 7.5 0 0 0-4.3-1.1c-1.3 0-2.4.2-3.3.5-.9.4-1.6 1-2 1.6a3.5 3.5 0 0 0-.3 4c.3.5.7.9 1.3 1.2l1.8 1 2 .5 3.2.8c1.3.3 2.5.7 3.7 1.2a13 13 0 0 1 3.2 1.8 8.1 8.1 0 0 1 3 6.5c0 2-.5 3.7-1.5 5.1a10 10 0 0 1-4.4 3.5c-1.8.8-4.1 1.2-6.8 1.2-2.6 0-4.9-.4-6.8-1.2-2-.8-3.4-2-4.5-3.5a10 10 0 0 1-1.7-5.6h6a5 5 0 0 0 3.5 4.6c1 .4 2.2.6 3.4.6 1.3 0 2.5-.2 3.5-.6 1-.4 1.8-1 2.4-1.7a4 4 0 0 0 .8-2.4c0-.9-.2-1.6-.7-2.2a11 11 0 0 0-2.1-1.4l-3.2-1-3.8-1c-2.8-.7-5-1.7-6.6-3.2a7.2 7.2 0 0 1-2.4-5.7 8 8 0 0 1 1.7-5 10 10 0 0 1 4.3-3.5c2-.8 4-1.2 6.4-1.2 2.3 0 4.4.4 6.2 1.2 1.8.8 3.2 2 4.3 3.4 1 1.4 1.5 3 1.5 5h-5.8z"/></svg>
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
|
2
|
+
<!-- Created with Pixodesk SVG (https://pixodesk.com) -->
|
|
3
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="285.3 235.00000000000003 236.5499999999999 315" text-rendering="geometricPrecision" shape-rendering="geometricPrecision">
|
|
4
|
+
<path fill="#df9f00" d="M466.9,238.8C464.4,240.3,458,244,452.7,247L443.1,252.4L453.3,258.4C458.9,261.6,468,267,473.5,270.2C484.9,276.9,482.3,277.2,498,268C503.2,265,508.7,261.9,510.2,261.1L513,259.7L509.6,257.4C506.7,255.4,490,245.6,477.3,238.4C475,237.1,472.8,236,472.3,236C471.9,236.1,469.4,237.3,466.9,238.8M404.1,275.1C400.7,277.2,397.6,279,397.4,279C397.2,279,394,280.8,390.4,283C386.8,285.2,383.3,287,382.6,287C382,287,384.9,289,389,291.4C393.1,293.7,402.1,299,409,302.9C415.9,306.9,421.9,310.5,422.3,311C422.8,311.4,426.2,309.9,429.8,307.7C439.4,301.9,448.2,297,450.3,296.3C453,295.5,452.3,293.5,448.8,291.7C445.1,289.8,441.6,287.8,424.9,277.9C418.5,274.1,412.7,271,411.9,271.1C411.1,271.1,407.6,272.9,404.1,275.1M453.4,304.5C453.4,310.5,453.5,312.9,453.7,309.7C453.9,306.5,453.9,301.6,453.7,298.7C453.5,295.8,453.4,298.4,453.4,304.5M350.4,306C349.5,306.9,340.8,311.9,328.3,318.7C324.8,320.6,322,322.3,322,322.5C322,322.8,330.1,327.6,341,333.7C342.9,334.8,348.5,338.1,353.3,340.9C358.2,343.8,362.8,345.9,363.6,345.6C364.4,345.3,369.3,342.6,374.6,339.5C379.8,336.5,384.3,334,384.5,334C384.7,334,386.5,332.9,388.4,331.6C391,329.9,391.6,329,390.7,328.1C390.1,327.5,389.3,327,388.8,327C388.4,327,384.1,324.6,379.3,321.7C374.4,318.7,368.4,315.2,366,313.8C363.5,312.4,359.2,309.9,356.4,308.2C353.5,306.5,350.8,305.6,350.4,306"/>
|
|
5
|
+
<path fill="#bf2020" d="M431.5,259.1C426,262.5,419.5,266.2,417.1,267.4L412.6,269.7L415.1,271.3C416.4,272.1,425.7,277.6,435.8,283.5L454.1,294.1L458.3,291.7C470.3,284.8,482.6,277.7,483,277.2C483.6,276.7,485,277.6,461.6,264C451.2,257.9,442.5,253,442.1,253C441.8,253.1,437,255.8,431.5,259.1M373.5,292.6C369.1,295.2,364.2,298,362.5,299C360.9,300,358,301.6,356.3,302.6C354.5,303.7,353,304.8,353,305.1C353,305.4,354.9,306.7,357.3,308C359.6,309.3,365.8,312.9,371,316C376.2,319.1,383.4,323.3,387.1,325.3L393.6,329L407.5,321C415.1,316.6,421.5,312.8,421.8,312.5C422.4,311.9,419.8,309.8,416,307.8C414.7,307.1,406.4,302.3,397.7,297.2C389,292.2,381.7,288,381.7,288C381.6,288,377.9,290.1,373.5,292.6M314,326.9C310.4,329,305.5,331.9,303,333.4C300.5,334.8,297,336.8,295.3,337.7C291.3,339.7,291.2,340.4,294.8,342.3C296.3,343.1,301.6,346.1,306.5,349C316.3,354.7,318.7,356.1,327.1,360.9L332.8,364.1L347.3,355.8C355.3,351.2,361.6,347.2,361.4,346.8C361.2,346.5,359.1,345.1,356.7,343.7C354.4,342.4,345.4,337.2,336.7,332.1C328,327.1,320.8,323,320.7,323.1C320.6,323.2,317.6,324.9,314,326.9"/>
|
|
6
|
+
<path fill="#bf6020" d="M362.4,359C362.4,365.9,362.5,368.7,362.7,365.2C362.9,361.8,362.9,356.2,362.7,352.7C362.5,349.3,362.4,352.1,362.4,359"/>
|
|
7
|
+
<path fill="#bf8000" d="M423.3,327C423.3,329.5,423.5,330.5,423.7,329.2C423.9,328,423.9,326,423.7,324.7C423.5,323.5,423.3,324.5,423.3,327"/>
|
|
8
|
+
<path fill="#dfbf00" d="M504.8,263.8C503.2,264.8,502,265.9,502,266.3C502,267,505.7,264.8,508.9,262.3C510.3,261.3,507.1,262.4,504.8,263.8M485.1,275.4C484.3,276.3,484,280.2,484.3,286.6L484.6,296.5L484.8,286.4C485,277,485.2,276.2,487.3,275.2C488.9,274.4,489,274.1,487.9,274.1C487,274,485.7,274.6,485.1,275.4M435,304.5C433.1,305.9,431.9,307,432.2,307C433.1,307,440,302.9,440,302.4C440,301.5,438.3,302.2,435,304.5M427.3,309.1C423.8,311.3,422.7,312.8,425.8,311.1C428,309.8,431.6,307.1,431,307C430.7,307,429,308,427.3,309.1M389.8,330.6C388.2,331.4,387,332.3,387,332.5C387,333.5,388.6,332.9,391,331C394,328.7,393.5,328.5,389.8,330.6M368.2,343.1C365.3,344.8,363.1,346.4,363.3,346.7C363.7,347.1,374,341,374,340.4C374,339.8,373.7,339.9,368.2,343.1"/>
|
|
9
|
+
<path fill="#ffbf00" d="M507.6,263.5C504.4,265.4,501.6,267,501.3,267C501.1,267,499,268.3,496.7,269.8C494.4,271.3,490.8,273.5,488.7,274.5L484.9,276.5L485.2,288.9C485.5,301.1,485.5,301.3,488.2,303.3C492.3,306.4,498,304.4,504.6,297.6C512.1,289.9,514.1,283.7,513.8,270.3L513.5,259.9L507.6,263.5zM445,299.8C440.9,302.2,435.6,305.3,433.2,306.6C423.4,312.1,424,311,424,322.5C424,328.2,424.4,334,425,335.4C428.3,344.3,441.8,338.9,449.6,325.5C452.2,321,452.5,319.4,452.8,310C453.4,293.9,453.8,294.5,445,299.8M388,332.6C385.5,334,378.9,337.9,373.3,341.3L363,347.4L363,357.7C363,369.7,364.3,373.5,368.8,374.5C375.5,375.9,385.1,368.6,390,358.2C391.8,354.5,392.4,351.1,392.8,341.7C393.1,335.3,393.1,330,392.9,330C392.7,330.1,390.5,331.2,388,332.6"/>
|
|
10
|
+
<path fill="#df9f20" d="M382.9,368.7L379.5,372.5L383.3,369.1C385.3,367.2,387,365.5,387,365.3C387,364.5,386.2,365.3,382.9,368.7"/>
|
|
11
|
+
<path fill="#9f2020" d="M479.5,279.4C477.3,280.7,470.7,284.5,464.8,288L454,294.3L454,305.5C454,315.3,454.3,317.1,456.1,319.4C460.7,325.3,471.2,321.3,478.3,310.8C482.7,304.3,484.2,297.2,483.8,285.8L483.5,277L479.5,279.4zM407.8,320.8L393,329.3L393,339.9C393,351.5,394,354.8,398.1,356.7C403.8,359.4,414.1,352.8,419.5,342.9C422.1,338.1,422.4,336.3,422.8,326.5C423,320.4,423.1,314.7,422.9,313.9C422.6,312.6,419.2,314.2,407.8,320.8M346.7,356.1C339.1,360.4,332.6,363.8,332.2,363.6C331.8,363.4,331.7,368.8,332,375.7C332.5,387.3,332.7,388.3,334.9,390.1C338.5,393,343.9,392.6,349.1,388.8C354.1,385.2,355.4,383.7,359.3,376.7C361.8,372.1,362,370.9,362,359.9C362,353.3,361.7,348,361.3,348C360.8,348.1,354.3,351.7,346.7,356.1"/>
|
|
12
|
+
<path fill="#df8020" d="M505.5,297.7C501.5,302.4,495.3,305.9,491.3,306C490.1,306,488,304.6,486.6,303L484.1,299.9L483.4,302.5C483.1,303.9,481,307.7,478.9,311C470.6,323.9,457.4,327,453.7,317C453.3,316,453.1,316.2,453.1,317.7C452.9,323.7,445.6,334.1,438.8,337.9C431.9,341.9,426.1,340.9,424,335.5C423.2,333.7,423.1,333.7,423.1,335.4C423,338.3,418.2,347.2,414.7,350.8C407.8,358,398.8,360.2,395.2,355.5L393.2,352.9L390.7,358.3C387.5,365.3,382.3,371,376.6,373.9C372.6,375.9,371.5,376,368.5,375C366.6,374.4,364.6,373.1,364.1,372.2C363.6,371.3,363,370.7,362.8,371C362.6,371.3,361.2,374,359.7,377.1C358.2,380.2,355.5,384.2,353.7,386C346.2,393.7,340.3,394.9,332.3,390.1C326.9,386.9,308.1,375.9,300.6,371.6L295.7,368.7L296.4,379.1C297,388.8,297.2,389.6,299.8,391.7C301.3,393,302.9,394,303.3,394C303.8,394,305.4,394.8,306.8,395.8C308.3,396.9,312,399,315,400.7C318,402.3,324.4,406,329.1,408.9L337.7,414L346.6,408.9C351.5,406,357.1,402.8,359,401.8C360.9,400.7,363,399.4,363.5,399C364.1,398.6,367,396.9,370,395.3C374.6,392.8,385.8,386.3,403.5,376C419.9,366.4,424.2,364,425,364C425.6,364,426,380.1,426,406.3L426,448.7L420.8,451.5C417.9,453,412.1,456.4,408,458.8C403.9,461.3,397.6,465,394,467C390.4,469,385.8,471.6,383.7,472.8C381.6,474,376.9,476.8,373.2,478.9C369.5,481,362.5,485,357.5,487.9C352.6,490.7,347.4,493.7,346,494.6C337.8,499.6,338.1,499.5,330.5,494.9C319.9,488.5,298.1,476,297.5,476C297.2,476,297.1,482.8,297.2,491.1C297.4,503.4,297.8,506.4,299,507.1C299.8,507.6,305.8,511.1,312.4,515C318.9,518.8,324.4,522,324.5,522C324.7,522,327.8,523.8,331.4,526L337.9,530L350.2,522.9C363,515.5,364.5,514.6,376.3,507.9C385.9,502.4,388.5,500.9,394.8,497.1C397.7,495.4,400.3,494,400.6,494C400.9,494,402.4,493.2,403.8,492.1C406.5,490.2,408.9,488.8,421,482C428.3,477.9,432.7,475.4,438.3,472L442,469.8L442,417.6C441.9,360.8,441.6,363.7,447.9,351C453.6,339.5,462,330.6,471.5,325.9C482,320.8,490.5,322.6,495.4,331C497.4,334.4,497.5,336.1,497.8,385.7C497.9,413.9,498.3,437,498.6,437C498.9,437,501.6,435.5,504.6,433.8L510,430.5L510,361.8C510,323.9,509.9,293,509.8,293.1C509.6,293.1,507.7,295.2,505.5,297.7"/>
|
|
13
|
+
<path fill="#9f8060" fill-opacity="0.3" d="M510.3,309C510.3,312.6,510.5,314,510.7,312.2C510.9,310.5,510.9,307.5,510.7,305.7C510.5,304,510.3,305.4,510.3,309"/>
|
|
14
|
+
<path fill="#df8040" d="M296.3,385.5C296.3,388.2,296.5,389.3,296.7,387.7C296.9,386.2,296.9,384,296.7,382.7C296.5,381.5,296.3,382.7,296.3,385.5"/>
|
|
15
|
+
<path fill="#df8000" d="M423.3,317.5C423.3,320.8,423.5,322,423.7,320.2C423.9,318.4,423.9,315.7,423.7,314.2C423.5,312.7,423.3,314.2,423.3,317.5"/>
|
|
16
|
+
<path fill="#806040" fill-opacity="0.3" d="M510.3,335C510.3,337.5,510.5,338.5,510.7,337.2C510.9,336,510.9,334,510.7,332.7C510.5,331.5,510.3,332.5,510.3,335"/>
|
|
17
|
+
<path fill="#608040" d="M425.4,394.5C425.4,411,425.6,417.9,425.7,409.8C425.9,401.7,425.9,388.2,425.7,379.8C425.6,371.4,425.4,378,425.4,394.5"/>
|
|
18
|
+
<path fill="#608060" d="M497.4,357C497.4,365,497.6,368.2,497.7,364.2C497.9,360.3,497.9,353.7,497.7,349.7C497.6,345.8,497.4,349,497.4,357M425.4,434.5C425.4,440.5,425.5,442.9,425.7,439.7C425.9,436.5,425.9,431.6,425.7,428.7C425.5,425.8,425.4,428.4,425.4,434.5"/>
|
|
19
|
+
<path fill="#bf8040" d="M510.3,425C510.3,428,510.5,429.3,510.7,427.7C510.9,426.2,510.9,423.8,510.7,422.2C510.5,420.7,510.3,422,510.3,425"/>
|
|
20
|
+
<path fill="#408060" d="M497.4,382.5C497.4,389.1,497.5,391.7,497.7,388.2C497.9,384.8,497.9,379.4,497.7,376.2C497.5,373.1,497.4,375.9,497.4,382.5"/>
|
|
21
|
+
<path fill="#006060" d="M472.5,326.4C461.6,331.4,450.4,345,445.2,359.2L442.5,366.5L442.2,418.3L441.9,470L445.2,468.1C450.8,465,462.7,458.1,466.2,456.1C468,455.1,472.9,452.2,477,449.8C481.1,447.3,487.2,443.9,490.5,442C493.8,440.2,496.8,438.5,497.2,438.1C497.6,437.8,497.5,414.3,497.1,386C496.4,339.3,496.2,334.2,494.6,331.2C490.7,323.9,481.8,322,472.5,326.4M489,332.9C493.6,337.7,494,339.9,494,360.6C494,372.3,493.7,377,492.8,377.8C491,379.2,446.1,405,445.4,405C445.1,405,445,396.3,445.3,385.7C445.8,365.4,446.4,362.1,451.5,352C454.7,345.6,455.1,345.1,461.8,338.3C468,331.9,473.7,329.1,480.4,329C484.8,329,485.7,329.4,489,332.9M493.8,409.2L493.5,436.4L469.9,450L446.3,463.6L445.7,443.6C445.3,432.5,445,420.3,445,416.4L445,409.2L456.2,402.9C462.3,399.4,468.5,395.8,469.9,395C471.3,394.1,475.9,391.5,480,389.3C484.1,387,488.4,384.4,489.5,383.6C494,380.2,494,380.4,493.8,409.2M417.6,369C413.9,371.2,410.7,373,410.5,373C410.4,373,406.3,375.3,401.4,378.2C368,397.5,355.3,404.9,346.8,409.7C337.9,414.8,337,415.5,337,418.2C337,419.8,337.3,420.9,337.8,420.7C338.2,420.4,349.3,414,362.5,406.5C375.7,398.9,387,392.4,387.5,392C388.1,391.6,390.5,390.1,393,388.8C395.5,387.4,402.3,383.5,408.1,380.1C414,376.8,419.3,374,419.9,374C420.7,374,421,384.1,421,409.4L421,444.8L410.3,451C404.3,454.5,397.5,458.4,395,459.8C392.5,461.1,390.1,462.6,389.5,463C389,463.4,386.5,464.9,384,466.2C381.5,467.6,376.3,470.6,372.3,472.9C368.4,475.1,363.6,477.8,361.8,478.8C360,479.9,356.3,482.1,353.5,483.7C344.5,489.2,338.5,492.4,337.9,492.3C337.6,492.2,337.6,493.4,337.8,495.1C338,496.7,338.5,497.8,338.9,497.6C339.5,497.3,384.4,471.5,390.5,468C409.7,457,423.6,448.8,424.3,448.1C424.7,447.7,424.9,428.8,424.8,406.2L424.5,365L417.6,369z"/>
|
|
22
|
+
<path fill="#208060" d="M497.4,402.5C497.4,407.4,497.5,409.6,497.7,407.3C497.9,405,497.9,400.9,497.7,398.3C497.5,395.7,497.4,397.5,497.4,402.5"/>
|
|
23
|
+
<path fill="#008080" d="M445.3,427.5C445.3,431.9,445.5,433.6,445.7,431.2C445.9,428.9,445.9,425.3,445.7,423.2C445.5,421.2,445.3,423.1,445.3,427.5"/>
|
|
24
|
+
<path fill="#409f9f" d="M445.3,378C445.3,380.5,445.5,381.5,445.7,380.2C445.9,379,445.9,377,445.7,375.7C445.5,374.5,445.3,375.5,445.3,378"/>
|
|
25
|
+
<path fill="#20809f" d="M445.3,390.5C445.3,394.9,445.5,396.6,445.7,394.2C445.9,391.9,445.9,388.3,445.7,386.2C445.5,384.2,445.3,386.1,445.3,390.5M420.4,425C420.4,435.7,420.6,440.1,420.7,434.7C420.9,429.4,420.9,420.6,420.7,415.2C420.6,409.9,420.4,414.3,420.4,425"/>
|
|
26
|
+
<path fill="#80bfdf" d="M472,331.8C463.4,336,455.8,344.3,450.6,355.4C446.8,363.7,446,369,446,387.6L446,403.9L450.8,401.2C453.5,399.7,464.3,393.5,474.9,387.5L494.1,376.5L493.7,359C493.3,339.9,492.5,336.1,487.5,332.2C484.1,329.5,477.1,329.3,472,331.8M491,372.3C491,375.1,489.3,377,486.8,377C484.8,377,484.5,374.9,486,371.9C487.5,369.1,491,369.4,491,372.3M418.5,374.9C417.6,375.7,398.2,387,386,393.8C379.4,397.5,351.1,413.9,347,416.4C345.1,417.6,342.3,419.2,340.8,419.9L338,421.3L338,456.2C338,475.3,338.3,491,338.6,491C339.2,491,342.9,489,356.4,481.1C361.3,478.3,365.5,476,365.7,476C365.9,476,368,474.7,370.3,473.1C372.6,471.6,376.8,469.1,379.5,467.6C382.3,466.2,387,463.5,390,461.8C393.1,460,401.1,455.3,407.8,451.5L420,444.5L420,409.2C420,389.9,419.9,374,419.7,374C419.5,374,419,374.4,418.5,374.9M367.8,421C372.3,422.7,374,423.7,373.8,424.9C373.6,425.8,372.7,426.6,371.7,426.8C369.9,427.2,359.4,423.5,357.6,421.9C356.4,420.9,358.9,417.8,360.4,418.3C361,418.5,364.3,419.8,367.8,421M355,426.4C361.4,428.7,363.8,431.4,360.6,432.6C359.3,433.1,345,428.4,345,427.5C345,426.1,346.4,424,347.4,424C348,424,351.4,425.1,355,426.4M366.8,432.9C366.4,434.9,362,435.7,362,433.8C362,431.8,364,430.1,365.7,430.7C366.5,431,367,432,366.8,432.9M374.8,434.1C377.3,435.1,377.8,437.8,375.5,438.6C373.8,439.3,367,437.2,367,436C367,435.5,367.3,434.4,367.6,433.6C368.2,431.9,369.1,432,374.8,434.1M357.1,439.4C363.3,441.7,365.8,444.4,362.7,445.5C361.3,446.1,350.3,442.5,348.6,440.9C347.6,440,348.5,437,349.8,437C350.3,437,353.6,438.1,357.1,439.4M492.5,382.9C491.9,383.4,489,385.1,486,386.8C483,388.4,477,391.9,472.7,394.4C468.4,396.9,464.8,399,464.6,399C464.4,399,460.3,401.4,455.4,404.3L446.5,409.6L446.2,435.9C446,460,446.1,462.2,447.6,461.6C448.5,461.3,458.3,455.7,469.4,449.3C480.4,442.9,490.5,437.2,491.7,436.6C494,435.5,494,435.5,494,408.7C494,394,493.9,382,493.7,382C493.5,382,493,382.4,492.5,382.9"/>
|
|
27
|
+
<path fill="#208080" d="M420.4,390C420.4,399.1,420.6,402.8,420.7,398.2C420.9,393.7,420.9,386.3,420.7,381.7C420.6,377.2,420.4,380.9,420.4,390M445,400.9C445,402.6,445.4,404.1,445.8,404.4C446.2,404.6,446.4,403.3,446.2,401.4C445.7,397.2,445,396.9,445,400.9M445.3,415C445.3,418,445.5,419.3,445.7,417.7C445.9,416.2,445.9,413.8,445.7,412.2C445.5,410.7,445.3,412,445.3,415"/>
|
|
28
|
+
<path fill="#806040" fill-opacity="0.4" d="M510.4,349C510.4,354.2,510.5,356.4,510.7,353.7C510.9,351.1,510.9,346.9,510.7,344.2C510.5,341.6,510.4,343.8,510.4,349"/>
|
|
29
|
+
<path fill="#802000" d="M291,351C291,357.9,291.5,362,292.5,363.9C293.6,366.2,301.5,371.8,306.5,373.9C307,374.1,311.3,376.7,316,379.5C331.6,388.9,334.2,390.3,333,388.1C332.5,387,332,381.2,332,375.1L332,364.1L323.9,359.6C319.4,357.1,315.6,355,315.4,355C315.2,355,312.2,353.3,308.8,351.2C305.3,349.1,299.9,345.9,296.8,344.1L291,340.9L291,351z"/>
|
|
30
|
+
<path fill="#200000" fill-opacity="0.3" d="M290.4,352.5C290.4,358,290.5,360.1,290.7,357.2C290.9,354.3,290.9,349.8,290.7,347.2C290.5,344.6,290.4,347,290.4,352.5"/>
|
|
31
|
+
<path fill="#806040" fill-opacity="0.5" d="M510.3,363C510.3,366,510.5,367.3,510.7,365.7C510.9,364.2,510.9,361.8,510.7,360.2C510.5,358.7,510.3,360,510.3,363"/>
|
|
32
|
+
<path fill="#dfdfdf" d="M358.7,419.7C357.2,421.1,358.3,421.9,365.1,424.5C370.9,426.8,372.4,427,373.1,425.9C374.1,424.2,373.7,423.8,367.1,421.2C361.4,418.9,359.8,418.6,358.7,419.7M346,426.3C346,428.1,359.2,433,360.8,431.8C362.9,430,362.2,429.5,355.6,427.2C348.3,424.7,346,424.5,346,426.3M362.7,432.4C362.4,433.2,362.7,434.1,363.5,434.4C365,434.9,367.4,432.7,366.3,431.7C365.2,430.5,363.2,430.9,362.7,432.4M367.5,434.1C367.1,434.7,367,435.3,367.2,435.5C368.3,436.6,374.8,438.8,375.4,438.3C376.7,437,375.9,435.3,373.5,434.2C370.3,432.7,368.3,432.7,367.5,434.1M348.6,437.8C347.2,439.2,348.2,441,350.8,441.6C352.3,442,355.4,443.1,357.7,444.1C361.3,445.6,362,445.7,363.1,444.4C363.7,443.6,364.1,442.7,363.8,442.5C362.8,441.4,349.1,437.2,348.6,437.8"/>
|
|
33
|
+
<path fill="#ffffff" d="M486.6,372.6C485.7,375,485.9,376,487.4,376C488.7,376,490,374,490,371.9C490,370.4,487.3,370.9,486.6,372.6"/>
|
|
34
|
+
<path fill="#806020" fill-opacity="0.6" d="M510.4,381.5C510.4,387,510.5,389.1,510.7,386.2C510.9,383.3,510.9,378.8,510.7,376.2C510.5,373.6,510.4,376,510.4,381.5"/>
|
|
35
|
+
<path fill="#804020" fill-opacity="0.6" d="M510.4,405C510.4,413,510.6,416.2,510.7,412.2C510.9,408.3,510.9,401.7,510.7,397.7C510.6,393.8,510.4,397,510.4,405"/>
|
|
36
|
+
<path fill="#006040" d="M297,394.4C297,396.5,298.6,397.8,306.8,402.6C312.1,405.7,320.8,410.8,326,413.9C331.2,417,335.8,419.6,336.2,419.8C336.7,420,337,418.8,337,417.2C337,414.9,336.3,413.8,334.3,412.8C332.7,412,324,407,314.8,401.7C305.6,396.4,297.8,392,297.5,392C297.2,392,297,393.1,297,394.4M297.2,471.6C297.4,473.9,299.3,475.4,308.9,481.2C315.1,484.9,320.4,488,320.6,488C320.7,488,324.6,490.3,329.2,493C335.7,496.9,337.6,497.7,337.8,496.4C338,495.6,337.9,494.3,337.7,493.6C337.3,492.6,318.2,480.6,314.5,479.1C314,478.9,310.8,477,307.5,475C296.7,468.4,296.8,468.4,297.2,471.6"/>
|
|
37
|
+
<path fill="#609f9f" fill-opacity="0.9" d="M296.3,402C296.3,404.5,296.5,405.5,296.7,404.2C296.9,403,296.9,401,296.7,399.7C296.5,398.5,296.3,399.5,296.3,402"/>
|
|
38
|
+
<path fill="#60809f" d="M297,432.8L297,467.6L304.3,472C317,479.6,336.9,491,337.6,491C338,491,338,475.4,337.7,456.2C337.2,429.5,336.8,421.3,335.8,420.7C323.2,413.2,312.6,407,312.4,407C312.2,407,309.7,405.5,306.8,403.6C303.9,401.8,300.5,399.8,299.3,399.1L297,397.9L297,432.8z"/>
|
|
39
|
+
<path fill="#609f9f" fill-opacity="0.8" d="M296.4,415C296.4,420.2,296.5,422.4,296.7,419.7C296.9,417.1,296.9,412.9,296.7,410.2C296.5,407.6,296.4,409.8,296.4,415"/>
|
|
40
|
+
<path fill="#80809f" d="M511,425.5L511,431.1L515,428.9C517.2,427.6,519,426.2,519,425.6C519,425,517.2,423.5,515,422.2L511,419.9L511,425.5zM291.2,509.7L286.8,512.3L292.7,515.5C295.9,517.3,301.2,520.4,304.5,522.4C321,532.4,329,536.8,329.5,536C329.7,535.6,331.5,534.5,333.5,533.6C335.4,532.7,337,531.5,337,531.1C337,530.6,330.1,526.3,321.8,521.4C313.4,516.6,304.5,511.3,302.1,509.8C299.6,508.3,297.1,507,296.6,507C296,507.1,293.5,508.3,291.2,509.7"/>
|
|
41
|
+
<path fill="#609f9f" d="M337.4,432C337.4,438.3,337.5,440.9,337.7,437.7C337.9,434.6,337.9,429.4,337.7,426.2C337.5,423.1,337.4,425.7,337.4,432"/>
|
|
42
|
+
<path fill="#406060" fill-opacity="0.6" d="M520.3,432C520.3,436.1,520.5,437.8,520.7,435.7C520.9,433.7,520.9,430.3,520.7,428.2C520.5,426.2,520.3,427.9,520.3,432"/>
|
|
43
|
+
<path fill="#808080" d="M514,429.3C511,431,503.6,435.2,497.5,438.8C485.4,446,483.7,446.9,473.5,452.8C469.7,455,464.5,457.9,462,459.4C459.5,460.8,453,464.5,447.5,467.6C442,470.7,431.9,476.5,425,480.5C403.9,492.8,347.7,525.2,344,527.3C342.1,528.3,339.9,529.7,339.3,530.2C338.6,530.8,338,530.9,338,530.4C338,529.9,337.5,530.3,336.9,531.2C336.4,532.2,335.4,533,334.9,533C334.3,533,332.5,533.9,330.9,535.1C328.3,537,328,537.7,328,543.1C328,546.3,328.4,549,328.8,549C329.3,549,334.5,546.1,340.5,542.5C346.4,538.9,351.4,536,351.6,536C351.8,536,354.3,534.6,357.2,532.9C360.1,531.1,365,528.3,368,526.7C371,525,376.4,521.9,379.9,519.8C383.3,517.7,386.6,516,387.1,516C387.6,516,388,515.6,388,515.1C388,514.6,388.8,514,389.8,513.7C392,513,403.4,506.5,407,504C408.6,502.9,410.1,502,410.5,502C410.9,502,412.4,501.2,413.8,500.2C415.3,499.1,419,496.9,422,495.3C425,493.6,433.1,489,440,485C446.9,481,455,476.4,458,474.7C461,473.1,464.7,470.9,466.2,469.8C467.6,468.8,469,468,469.3,468C469.6,468,474.4,465.3,479.9,462C485.5,458.7,490.5,456,491,456C491.6,456,492,455.5,492,455C492,454.4,492.4,454,492.9,454C493.8,454,517.9,440.4,519.3,439.1C519.7,438.6,520,435.6,519.8,432.3L519.5,426.3L514,429.3z"/>
|
|
44
|
+
<path fill="#809f9f" fill-opacity="0.7" d="M296.3,432.5C296.3,435.2,296.5,436.3,296.7,434.7C296.9,433.2,296.9,431,296.7,429.7C296.5,428.5,296.3,429.7,296.3,432.5"/>
|
|
45
|
+
<path fill="#809fbf" fill-opacity="0.7" d="M296.3,447C296.3,451.1,296.5,452.8,296.7,450.7C296.9,448.7,296.9,445.3,296.7,443.2C296.5,441.2,296.3,442.9,296.3,447"/>
|
|
46
|
+
<path fill="#404040" fill-opacity="0.4" d="M286.3,516C286.3,518.5,286.5,519.5,286.7,518.2C286.9,517,286.9,515,286.7,513.7C286.5,512.5,286.3,513.5,286.3,516"/>
|
|
47
|
+
<path fill="#809fbf" fill-opacity="0.6" d="M296.3,461C296.3,465.1,296.5,466.8,296.7,464.7C296.9,462.7,296.9,459.3,296.7,457.2C296.5,455.2,296.3,456.9,296.3,461"/>
|
|
48
|
+
<path fill="#df9f80" fill-opacity="0.6" d="M296.4,488C296.4,495.4,296.5,498.5,296.7,494.7C296.9,491,296.9,485,296.7,481.2C296.5,477.5,296.4,480.6,296.4,488"/>
|
|
49
|
+
<path fill="#606060" d="M287.2,519.5C287.4,524.3,287.9,526.5,289,527.1C289.8,527.6,293.6,529.7,297.4,532C301.1,534.2,304.4,536,304.7,536C305,536,306.4,536.8,307.8,537.9C310.4,539.7,315.2,542.4,323.5,546.9L327.5,549L327.8,543.9C328.2,537.2,328,536.7,322.8,533.8C303.8,522.9,292.1,516.1,289.7,514.5L286.9,512.7L287.2,519.5z"/>
|
|
50
|
+
</svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1155 1000"><path d="m577.3 0 577.4 1000H0z" fill="#fff"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16"><path fill-rule="evenodd" clip-rule="evenodd" d="M1.5 2.5h13v10a1 1 0 0 1-1 1h-11a1 1 0 0 1-1-1zM0 1h16v11.5a2.5 2.5 0 0 1-2.5 2.5h-11A2.5 2.5 0 0 1 0 12.5zm3.75 4.5a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5M7 4.75a.75.75 0 1 1-1.5 0 .75.75 0 0 1 1.5 0m1.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5" fill="#666"/></svg>
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { notFound, redirect } from 'next/navigation'
|
|
2
|
+
import { cookies } from 'next/headers'
|
|
3
|
+
import { findBackendMatch } from '@open-mercato/shared/modules/registry'
|
|
4
|
+
import { modules } from '@/.mercato/generated/modules.generated'
|
|
5
|
+
import { getAuthFromCookies } from '@open-mercato/shared/lib/auth/server'
|
|
6
|
+
import { ApplyBreadcrumb } from '@open-mercato/ui/backend/AppShell'
|
|
7
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
8
|
+
import { resolveFeatureCheckContext } from '@open-mercato/core/modules/directory/utils/organizationScope'
|
|
9
|
+
import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'
|
|
10
|
+
|
|
11
|
+
type Awaitable<T> = T | Promise<T>
|
|
12
|
+
|
|
13
|
+
export default async function BackendCatchAll(props: { params: Awaitable<{ slug?: string[] }> }) {
|
|
14
|
+
const params = await props.params
|
|
15
|
+
const pathname = '/backend/' + (params.slug?.join('/') ?? '')
|
|
16
|
+
const match = findBackendMatch(modules, pathname)
|
|
17
|
+
if (!match) return notFound()
|
|
18
|
+
if (match.route.requireAuth) {
|
|
19
|
+
const auth = await getAuthFromCookies()
|
|
20
|
+
if (!auth) redirect('/api/auth/session/refresh?redirect=' + encodeURIComponent(pathname))
|
|
21
|
+
const required = match.route.requireRoles || []
|
|
22
|
+
if (required.length) {
|
|
23
|
+
const roles = auth.roles || []
|
|
24
|
+
const ok = required.some(r => roles.includes(r))
|
|
25
|
+
if (!ok) redirect('/login?requireRole=' + encodeURIComponent(required.join(',')))
|
|
26
|
+
}
|
|
27
|
+
const features = match.route.requireFeatures
|
|
28
|
+
if (features && features.length) {
|
|
29
|
+
const container = await createRequestContainer()
|
|
30
|
+
const rbac = container.resolve('rbacService') as RbacService
|
|
31
|
+
let organizationIdForCheck: string | null = auth.orgId ?? null
|
|
32
|
+
const cookieStore = await cookies()
|
|
33
|
+
const cookieSelected = cookieStore.get('om_selected_org')?.value ?? null
|
|
34
|
+
let tenantIdForCheck: string | null = auth.tenantId ?? null
|
|
35
|
+
try {
|
|
36
|
+
const { organizationId, allowedOrganizationIds, scope } = await resolveFeatureCheckContext({ container, auth, selectedId: cookieSelected })
|
|
37
|
+
organizationIdForCheck = organizationId
|
|
38
|
+
tenantIdForCheck = scope.tenantId ?? auth.tenantId ?? null
|
|
39
|
+
if (Array.isArray(allowedOrganizationIds) && allowedOrganizationIds.length === 0) {
|
|
40
|
+
redirect('/login?requireFeature=' + encodeURIComponent(features.join(',')))
|
|
41
|
+
}
|
|
42
|
+
} catch {
|
|
43
|
+
organizationIdForCheck = auth.orgId ?? null
|
|
44
|
+
tenantIdForCheck = auth.tenantId ?? null
|
|
45
|
+
}
|
|
46
|
+
const ok = await rbac.userHasAllFeatures(auth.sub, features, { tenantId: tenantIdForCheck, organizationId: organizationIdForCheck })
|
|
47
|
+
if (!ok) redirect('/login?requireFeature=' + encodeURIComponent(features.join(',')))
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
const Component = match.route.Component
|
|
51
|
+
return (
|
|
52
|
+
<>
|
|
53
|
+
<ApplyBreadcrumb breadcrumb={match.route.breadcrumb} title={match.route.title} titleKey={match.route.titleKey} />
|
|
54
|
+
<Component params={match.params} />
|
|
55
|
+
</>
|
|
56
|
+
)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export const dynamic = 'force-dynamic'
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
import { cookies, headers } from 'next/headers'
|
|
2
|
+
import Script from 'next/script'
|
|
3
|
+
import type { ReactNode } from 'react'
|
|
4
|
+
import { modules } from '@/.mercato/generated/modules.generated'
|
|
5
|
+
import { findBackendMatch } from '@open-mercato/shared/modules/registry'
|
|
6
|
+
import { getAuthFromCookies } from '@open-mercato/shared/lib/auth/server'
|
|
7
|
+
import { AppShell } from '@open-mercato/ui/backend/AppShell'
|
|
8
|
+
import { buildAdminNav } from '@open-mercato/ui/backend/utils/nav'
|
|
9
|
+
import type { AdminNavItem } from '@open-mercato/ui/backend/utils/nav'
|
|
10
|
+
import { UserMenu } from '@open-mercato/ui/backend/UserMenu'
|
|
11
|
+
import { GlobalSearchDialog } from '@open-mercato/search/modules/search/frontend'
|
|
12
|
+
import OrganizationSwitcher from '@/components/OrganizationSwitcher'
|
|
13
|
+
import { resolveTranslations } from '@open-mercato/shared/lib/i18n/server'
|
|
14
|
+
import { I18nProvider } from '@open-mercato/shared/lib/i18n/context'
|
|
15
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
16
|
+
import {
|
|
17
|
+
applySidebarPreference,
|
|
18
|
+
loadFirstRoleSidebarPreference,
|
|
19
|
+
loadSidebarPreference,
|
|
20
|
+
} from '@open-mercato/core/modules/auth/services/sidebarPreferencesService'
|
|
21
|
+
import type { SidebarPreferencesSettings } from '@open-mercato/shared/modules/navigation/sidebarPreferences'
|
|
22
|
+
import { Role } from '@open-mercato/core/modules/auth/data/entities'
|
|
23
|
+
import type { EntityManager } from '@mikro-orm/postgresql'
|
|
24
|
+
import type { FilterQuery } from '@mikro-orm/core'
|
|
25
|
+
import type { AwilixContainer } from 'awilix'
|
|
26
|
+
import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'
|
|
27
|
+
import { resolveFeatureCheckContext } from '@open-mercato/core/modules/directory/utils/organizationScope'
|
|
28
|
+
import { APP_VERSION } from '@open-mercato/shared/lib/version'
|
|
29
|
+
import { PageInjectionBoundary } from '@open-mercato/ui/backend/injection/PageInjectionBoundary'
|
|
30
|
+
|
|
31
|
+
type NavItem = {
|
|
32
|
+
href: string
|
|
33
|
+
title: string
|
|
34
|
+
defaultTitle: string
|
|
35
|
+
enabled: boolean
|
|
36
|
+
hidden?: boolean
|
|
37
|
+
icon?: ReactNode
|
|
38
|
+
children?: NavItem[]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
type NavGroup = {
|
|
42
|
+
id: string
|
|
43
|
+
name: string
|
|
44
|
+
defaultName: string
|
|
45
|
+
items: NavItem[]
|
|
46
|
+
weight: number
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export default async function BackendLayout({ children, params }: { children: React.ReactNode; params: Promise<{ slug?: string[] }> }) {
|
|
50
|
+
const auth = await getAuthFromCookies()
|
|
51
|
+
const cookieStore = await cookies()
|
|
52
|
+
const headerStore = await headers()
|
|
53
|
+
const rawSelectedOrg = cookieStore.get('om_selected_org')?.value
|
|
54
|
+
const rawSelectedTenant = cookieStore.get('om_selected_tenant')?.value
|
|
55
|
+
const selectedOrgForScope = rawSelectedOrg === undefined
|
|
56
|
+
? undefined
|
|
57
|
+
: rawSelectedOrg && rawSelectedOrg.trim().length > 0
|
|
58
|
+
? rawSelectedOrg
|
|
59
|
+
: null
|
|
60
|
+
const selectedTenantForScope = rawSelectedTenant === undefined
|
|
61
|
+
? undefined
|
|
62
|
+
: rawSelectedTenant && rawSelectedTenant.trim().length > 0
|
|
63
|
+
? rawSelectedTenant
|
|
64
|
+
: null
|
|
65
|
+
|
|
66
|
+
let requestContainer: AwilixContainer | null = null
|
|
67
|
+
const ensureContainer = async (): Promise<AwilixContainer> => {
|
|
68
|
+
if (!requestContainer) {
|
|
69
|
+
requestContainer = await createRequestContainer()
|
|
70
|
+
}
|
|
71
|
+
return requestContainer
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
let path = headerStore.get('x-next-url') ?? ''
|
|
75
|
+
if (path.includes('?')) path = path.split('?')[0]
|
|
76
|
+
let resolvedParams: { slug?: string[] } = {}
|
|
77
|
+
try {
|
|
78
|
+
resolvedParams = await params
|
|
79
|
+
} catch {
|
|
80
|
+
resolvedParams = {}
|
|
81
|
+
}
|
|
82
|
+
if (!path) {
|
|
83
|
+
const slug = resolvedParams.slug ?? []
|
|
84
|
+
path = '/backend' + (Array.isArray(slug) && slug.length ? '/' + slug.join('/') : '')
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const ctxAuth = auth
|
|
88
|
+
? {
|
|
89
|
+
roles: auth.roles || [],
|
|
90
|
+
sub: auth.sub,
|
|
91
|
+
tenantId: auth.tenantId,
|
|
92
|
+
orgId: auth.orgId,
|
|
93
|
+
}
|
|
94
|
+
: undefined
|
|
95
|
+
const ctx = { auth: ctxAuth, path }
|
|
96
|
+
|
|
97
|
+
const { translate, locale, dict } = await resolveTranslations()
|
|
98
|
+
const embeddingConfigured = Boolean(
|
|
99
|
+
process.env.OPENAI_API_KEY ||
|
|
100
|
+
process.env.GOOGLE_GENERATIVE_AI_API_KEY ||
|
|
101
|
+
process.env.MISTRAL_API_KEY ||
|
|
102
|
+
process.env.COHERE_API_KEY ||
|
|
103
|
+
process.env.AWS_ACCESS_KEY_ID ||
|
|
104
|
+
process.env.OLLAMA_BASE_URL
|
|
105
|
+
)
|
|
106
|
+
const missingConfigMessage = translate('search.messages.missingConfig', 'Search requires configuring an embedding provider for semantic search.')
|
|
107
|
+
|
|
108
|
+
const featureChecker = auth
|
|
109
|
+
? async (features: string[]): Promise<Set<string>> => {
|
|
110
|
+
if (!features?.length) return new Set()
|
|
111
|
+
try {
|
|
112
|
+
const container = await ensureContainer()
|
|
113
|
+
const rbac = container.resolve<RbacService>('rbacService')
|
|
114
|
+
const { organizationId, scope, allowedOrganizationIds } = await resolveFeatureCheckContext({
|
|
115
|
+
container,
|
|
116
|
+
auth,
|
|
117
|
+
selectedId: selectedOrgForScope,
|
|
118
|
+
tenantId: selectedTenantForScope,
|
|
119
|
+
})
|
|
120
|
+
if (Array.isArray(allowedOrganizationIds) && allowedOrganizationIds.length === 0) {
|
|
121
|
+
return new Set()
|
|
122
|
+
}
|
|
123
|
+
const tenantForCheck = scope.tenantId ?? auth.tenantId ?? null
|
|
124
|
+
const orgForCheck = organizationId ?? null
|
|
125
|
+
const context = { tenantId: tenantForCheck, organizationId: orgForCheck }
|
|
126
|
+
const hasAll = await rbac.userHasAllFeatures(auth.sub, features, context)
|
|
127
|
+
if (hasAll) return new Set(features)
|
|
128
|
+
const granted: string[] = []
|
|
129
|
+
for (const feature of features) {
|
|
130
|
+
const hasFeature = await rbac.userHasAllFeatures(auth.sub, [feature], context)
|
|
131
|
+
if (hasFeature) granted.push(feature)
|
|
132
|
+
}
|
|
133
|
+
return new Set(granted)
|
|
134
|
+
} catch {
|
|
135
|
+
return new Set()
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
: undefined
|
|
139
|
+
|
|
140
|
+
const entries = await buildAdminNav(
|
|
141
|
+
modules,
|
|
142
|
+
ctx,
|
|
143
|
+
undefined,
|
|
144
|
+
(key, fallback) => (key ? translate(key, fallback) : fallback),
|
|
145
|
+
featureChecker ? { checkFeatures: featureChecker } : undefined,
|
|
146
|
+
)
|
|
147
|
+
|
|
148
|
+
const groupMap = new Map<string, {
|
|
149
|
+
id: string
|
|
150
|
+
key?: string
|
|
151
|
+
name: string
|
|
152
|
+
defaultName: string
|
|
153
|
+
items: AdminNavItem[]
|
|
154
|
+
weight: number
|
|
155
|
+
}>()
|
|
156
|
+
for (const entry of entries) {
|
|
157
|
+
const weight = entry.priority ?? entry.order ?? 10_000
|
|
158
|
+
if (!groupMap.has(entry.groupId)) {
|
|
159
|
+
groupMap.set(entry.groupId, {
|
|
160
|
+
id: entry.groupId,
|
|
161
|
+
key: entry.groupKey,
|
|
162
|
+
name: entry.group,
|
|
163
|
+
defaultName: entry.groupDefaultName,
|
|
164
|
+
items: [entry],
|
|
165
|
+
weight,
|
|
166
|
+
})
|
|
167
|
+
} else {
|
|
168
|
+
const group = groupMap.get(entry.groupId)!
|
|
169
|
+
group.items.push(entry)
|
|
170
|
+
if (weight < group.weight) group.weight = weight
|
|
171
|
+
if (!group.key && entry.groupKey) group.key = entry.groupKey
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
const mapItem = (item: AdminNavItem): NavItem => ({
|
|
176
|
+
href: item.href,
|
|
177
|
+
title: item.title,
|
|
178
|
+
defaultTitle: item.defaultTitle,
|
|
179
|
+
enabled: item.enabled,
|
|
180
|
+
hidden: item.hidden,
|
|
181
|
+
icon: item.icon,
|
|
182
|
+
children: item.children?.map(mapItem),
|
|
183
|
+
})
|
|
184
|
+
|
|
185
|
+
const baseGroups: NavGroup[] = Array.from(groupMap.values()).map((group) => ({
|
|
186
|
+
id: group.id,
|
|
187
|
+
name: group.name,
|
|
188
|
+
defaultName: group.defaultName,
|
|
189
|
+
weight: group.weight,
|
|
190
|
+
items: group.items.map(mapItem),
|
|
191
|
+
}))
|
|
192
|
+
const defaultGroupOrder = [
|
|
193
|
+
'customers.nav.group',
|
|
194
|
+
'catalog.nav.group',
|
|
195
|
+
'customers~sales.nav.group',
|
|
196
|
+
'entities.nav.group',
|
|
197
|
+
'directory.nav.group',
|
|
198
|
+
'customers.storage.nav.group',
|
|
199
|
+
]
|
|
200
|
+
const groupOrderIndex = new Map(defaultGroupOrder.map((id, index) => [id, index]))
|
|
201
|
+
baseGroups.sort((a, b) => {
|
|
202
|
+
const aIndex = groupOrderIndex.get(a.id)
|
|
203
|
+
const bIndex = groupOrderIndex.get(b.id)
|
|
204
|
+
if (aIndex !== undefined || bIndex !== undefined) {
|
|
205
|
+
if (aIndex === undefined) return 1
|
|
206
|
+
if (bIndex === undefined) return -1
|
|
207
|
+
if (aIndex !== bIndex) return aIndex - bIndex
|
|
208
|
+
}
|
|
209
|
+
if (a.weight !== b.weight) return a.weight - b.weight
|
|
210
|
+
return a.name.localeCompare(b.name)
|
|
211
|
+
})
|
|
212
|
+
const defaultGroupCount = defaultGroupOrder.length
|
|
213
|
+
baseGroups.forEach((group, index) => {
|
|
214
|
+
const rank = groupOrderIndex.get(group.id)
|
|
215
|
+
const fallbackWeight = typeof group.weight === 'number' ? group.weight : 10_000
|
|
216
|
+
const normalized =
|
|
217
|
+
(rank !== undefined ? rank : defaultGroupCount + index) * 1_000_000 +
|
|
218
|
+
Math.min(Math.max(fallbackWeight, 0), 999_999)
|
|
219
|
+
group.weight = normalized
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
let rolePreference: SidebarPreferencesSettings | null = null
|
|
223
|
+
let sidebarPreference: SidebarPreferencesSettings | null = null
|
|
224
|
+
if (auth) {
|
|
225
|
+
try {
|
|
226
|
+
const container = await ensureContainer()
|
|
227
|
+
const em = container.resolve('em') as EntityManager
|
|
228
|
+
if (Array.isArray(auth.roles) && auth.roles.length) {
|
|
229
|
+
const roleScope: FilterQuery<Role> = auth.tenantId
|
|
230
|
+
? { $or: [{ tenantId: auth.tenantId }, { tenantId: null }] }
|
|
231
|
+
: { tenantId: null }
|
|
232
|
+
const roleRecords = await em.find(Role, {
|
|
233
|
+
name: { $in: auth.roles },
|
|
234
|
+
...roleScope,
|
|
235
|
+
})
|
|
236
|
+
const roleIds = roleRecords.map((role) => role.id)
|
|
237
|
+
if (roleIds.length) {
|
|
238
|
+
rolePreference = await loadFirstRoleSidebarPreference(em, {
|
|
239
|
+
roleIds,
|
|
240
|
+
tenantId: auth.tenantId ?? null,
|
|
241
|
+
locale,
|
|
242
|
+
})
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
sidebarPreference = await loadSidebarPreference(em, {
|
|
246
|
+
userId: auth.sub,
|
|
247
|
+
tenantId: auth.tenantId ?? null,
|
|
248
|
+
organizationId: auth.orgId ?? null,
|
|
249
|
+
locale,
|
|
250
|
+
})
|
|
251
|
+
} catch {
|
|
252
|
+
// ignore preference loading failures; render with default navigation
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const groupsWithRole = rolePreference ? applySidebarPreference(baseGroups, rolePreference) : baseGroups
|
|
257
|
+
const baseForUser = adoptSidebarDefaults(groupsWithRole)
|
|
258
|
+
const appliedGroups = sidebarPreference ? applySidebarPreference(baseForUser, sidebarPreference) : baseForUser
|
|
259
|
+
|
|
260
|
+
const materializeItem = (item: NavItem): NavItem => ({
|
|
261
|
+
href: item.href,
|
|
262
|
+
title: item.title,
|
|
263
|
+
defaultTitle: item.defaultTitle,
|
|
264
|
+
enabled: item.enabled,
|
|
265
|
+
hidden: item.hidden,
|
|
266
|
+
icon: item.icon,
|
|
267
|
+
children: item.children?.map(materializeItem),
|
|
268
|
+
})
|
|
269
|
+
|
|
270
|
+
const groups: NavGroup[] = appliedGroups.map((group) => ({
|
|
271
|
+
id: group.id,
|
|
272
|
+
name: group.name,
|
|
273
|
+
defaultName: group.defaultName,
|
|
274
|
+
items: group.items.map(materializeItem),
|
|
275
|
+
weight: group.weight,
|
|
276
|
+
}))
|
|
277
|
+
|
|
278
|
+
type NavEntry = NavItem & { group: string }
|
|
279
|
+
const allEntries: NavEntry[] = groups.flatMap((group) =>
|
|
280
|
+
group.items.map((item) => ({ ...item, group: group.name })),
|
|
281
|
+
)
|
|
282
|
+
const current = allEntries.find((item) => path.startsWith(item.href))
|
|
283
|
+
const currentTitle = current?.title || ''
|
|
284
|
+
const match = findBackendMatch(modules, path)
|
|
285
|
+
const rawBreadcrumb = match?.route.breadcrumb
|
|
286
|
+
const breadcrumb = rawBreadcrumb?.map((item) => {
|
|
287
|
+
const fallback = item.label
|
|
288
|
+
const label = item.labelKey ? translate(item.labelKey, fallback || item.labelKey) : fallback
|
|
289
|
+
return { ...item, label }
|
|
290
|
+
})
|
|
291
|
+
|
|
292
|
+
const collapsedCookie = cookieStore.get('om_sidebar_collapsed')?.value
|
|
293
|
+
const initialCollapsed = collapsedCookie === '1'
|
|
294
|
+
|
|
295
|
+
const rightHeaderContent = (
|
|
296
|
+
<>
|
|
297
|
+
<GlobalSearchDialog embeddingConfigured={embeddingConfigured} missingConfigMessage={missingConfigMessage} />
|
|
298
|
+
<OrganizationSwitcher />
|
|
299
|
+
<UserMenu email={auth?.email} />
|
|
300
|
+
</>
|
|
301
|
+
)
|
|
302
|
+
|
|
303
|
+
const productName = translate('appShell.productName', 'Open Mercato')
|
|
304
|
+
const injectionContext = {
|
|
305
|
+
path,
|
|
306
|
+
userId: auth?.sub ?? null,
|
|
307
|
+
tenantId: auth?.tenantId ?? null,
|
|
308
|
+
organizationId: auth?.orgId ?? null,
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
return (
|
|
312
|
+
<>
|
|
313
|
+
<Script async src="https://w.appzi.io/w.js?token=TtIV6" strategy="afterInteractive" />
|
|
314
|
+
<I18nProvider locale={locale} dict={dict}>
|
|
315
|
+
<AppShell
|
|
316
|
+
key={path}
|
|
317
|
+
productName={productName}
|
|
318
|
+
email={auth?.email}
|
|
319
|
+
groups={groups}
|
|
320
|
+
currentTitle={currentTitle}
|
|
321
|
+
breadcrumb={breadcrumb}
|
|
322
|
+
sidebarCollapsedDefault={initialCollapsed}
|
|
323
|
+
rightHeaderSlot={rightHeaderContent}
|
|
324
|
+
adminNavApi="/api/auth/admin/nav"
|
|
325
|
+
version={APP_VERSION}
|
|
326
|
+
>
|
|
327
|
+
<PageInjectionBoundary path={path} context={injectionContext}>
|
|
328
|
+
{children}
|
|
329
|
+
</PageInjectionBoundary>
|
|
330
|
+
</AppShell>
|
|
331
|
+
</I18nProvider>
|
|
332
|
+
</>
|
|
333
|
+
)
|
|
334
|
+
}
|
|
335
|
+
export const dynamic = 'force-dynamic'
|
|
336
|
+
|
|
337
|
+
function adoptSidebarDefaults(groups: NavGroup[]): NavGroup[] {
|
|
338
|
+
const adoptItems = (items: NavItem[]): NavItem[] =>
|
|
339
|
+
items.map((item) => ({
|
|
340
|
+
...item,
|
|
341
|
+
defaultTitle: item.title,
|
|
342
|
+
children: item.children ? adoptItems(item.children) : undefined,
|
|
343
|
+
}))
|
|
344
|
+
|
|
345
|
+
return groups.map((group) => ({
|
|
346
|
+
...group,
|
|
347
|
+
defaultName: group.name,
|
|
348
|
+
items: adoptItems(group.items),
|
|
349
|
+
}))
|
|
350
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { getAuthFromCookies } from '@open-mercato/shared/lib/auth/server'
|
|
2
|
+
import { redirect } from 'next/navigation'
|
|
3
|
+
import { DashboardScreen } from '@open-mercato/ui/backend/dashboard'
|
|
4
|
+
|
|
5
|
+
export default async function BackendIndex() {
|
|
6
|
+
const auth = await getAuthFromCookies()
|
|
7
|
+
if (!auth) redirect('/api/auth/session/refresh?redirect=/backend')
|
|
8
|
+
return (
|
|
9
|
+
<div className="p-6 space-y-6">
|
|
10
|
+
<DashboardScreen />
|
|
11
|
+
</div>
|
|
12
|
+
)
|
|
13
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { notFound, redirect } from 'next/navigation'
|
|
2
|
+
import { findFrontendMatch } from '@open-mercato/shared/modules/registry'
|
|
3
|
+
import { modules } from '@/.mercato/generated/modules.generated'
|
|
4
|
+
import { getAuthFromCookies } from '@open-mercato/shared/lib/auth/server'
|
|
5
|
+
import { createRequestContainer } from '@open-mercato/shared/lib/di/container'
|
|
6
|
+
import type { RbacService } from '@open-mercato/core/modules/auth/services/rbacService'
|
|
7
|
+
|
|
8
|
+
export default async function SiteCatchAll({ params }: { params: Promise<{ slug: string[] }> }) {
|
|
9
|
+
const p = await params
|
|
10
|
+
const pathname = '/' + (p.slug?.join('/') ?? '')
|
|
11
|
+
const match = findFrontendMatch(modules, pathname)
|
|
12
|
+
if (!match) return notFound()
|
|
13
|
+
if (match.route.requireAuth) {
|
|
14
|
+
const auth = await getAuthFromCookies()
|
|
15
|
+
if (!auth) redirect('/api/auth/session/refresh?redirect=' + encodeURIComponent(pathname))
|
|
16
|
+
const required = match.route.requireRoles || []
|
|
17
|
+
if (required.length) {
|
|
18
|
+
const roles = auth.roles || []
|
|
19
|
+
const ok = required.some(r => roles.includes(r))
|
|
20
|
+
if (!ok) redirect('/login?requireRole=' + encodeURIComponent(required.join(',')))
|
|
21
|
+
}
|
|
22
|
+
const features = match.route.requireFeatures
|
|
23
|
+
if (features && features.length) {
|
|
24
|
+
const container = await createRequestContainer()
|
|
25
|
+
const rbac = container.resolve('rbacService') as RbacService
|
|
26
|
+
const ok = await rbac.userHasAllFeatures(auth.sub, features, { tenantId: auth.tenantId, organizationId: auth.orgId })
|
|
27
|
+
if (!ok) redirect('/login?requireFeature=' + encodeURIComponent(features.join(',')))
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
const Component = match.route.Component
|
|
31
|
+
return <Component params={match.params} />
|
|
32
|
+
}
|