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.
Files changed (49) hide show
  1. package/README.md +94 -0
  2. package/bin/create-mercato-app +21 -0
  3. package/dist/index.js +177 -0
  4. package/package.json +42 -0
  5. package/template/.env.example +217 -0
  6. package/template/.yarnrc.yml.template +2 -0
  7. package/template/components.json +22 -0
  8. package/template/gitignore +50 -0
  9. package/template/next.config.ts +28 -0
  10. package/template/package.json.template +87 -0
  11. package/template/postcss.config.mjs +5 -0
  12. package/template/public/catch-the-tornado-logo.png +0 -0
  13. package/template/public/file.svg +1 -0
  14. package/template/public/globe.svg +1 -0
  15. package/template/public/next.svg +1 -0
  16. package/template/public/open-mercato.svg +50 -0
  17. package/template/public/vercel.svg +1 -0
  18. package/template/public/window.svg +1 -0
  19. package/template/src/app/(backend)/backend/[...slug]/page.tsx +59 -0
  20. package/template/src/app/(backend)/backend/layout.tsx +350 -0
  21. package/template/src/app/(backend)/backend/page.tsx +13 -0
  22. package/template/src/app/(frontend)/[...slug]/page.tsx +32 -0
  23. package/template/src/app/api/[...slug]/route.ts +227 -0
  24. package/template/src/app/api/docs/markdown/route.ts +35 -0
  25. package/template/src/app/api/docs/openapi/route.ts +30 -0
  26. package/template/src/app/globals.css +178 -0
  27. package/template/src/app/layout.tsx +76 -0
  28. package/template/src/app/page.tsx +134 -0
  29. package/template/src/bootstrap.ts +58 -0
  30. package/template/src/components/ClientBootstrap.tsx +37 -0
  31. package/template/src/components/GlobalNoticeBars.tsx +116 -0
  32. package/template/src/components/OrganizationSwitcher.tsx +360 -0
  33. package/template/src/components/StartPageContent.tsx +269 -0
  34. package/template/src/components/ui/button.tsx +59 -0
  35. package/template/src/components/ui/card.tsx +92 -0
  36. package/template/src/components/ui/checkbox.tsx +29 -0
  37. package/template/src/components/ui/input.tsx +21 -0
  38. package/template/src/components/ui/label.tsx +24 -0
  39. package/template/src/di.ts +11 -0
  40. package/template/src/i18n/de.json +375 -0
  41. package/template/src/i18n/en.json +376 -0
  42. package/template/src/i18n/es.json +376 -0
  43. package/template/src/i18n/pl.json +375 -0
  44. package/template/src/modules/.gitkeep +0 -0
  45. package/template/src/modules.ts +31 -0
  46. package/template/src/proxy.ts +17 -0
  47. package/template/tsconfig.json +54 -0
  48. package/template/types/pg/index.d.ts +1 -0
  49. 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
+ }
@@ -0,0 +1,5 @@
1
+ const config = {
2
+ plugins: ["@tailwindcss/postcss"],
3
+ };
4
+
5
+ export default config;
@@ -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
+ }