@nebulit/embuilder 0.1.39

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 (212) hide show
  1. package/README.md +254 -0
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +138 -0
  4. package/package.json +49 -0
  5. package/templates/.claude/hooks/QUICKSTART.md +256 -0
  6. package/templates/.claude/hooks/README.md +533 -0
  7. package/templates/.claude/hooks/analyze-commit.sh +22 -0
  8. package/templates/.claude/hooks/analyze-commit.ts +518 -0
  9. package/templates/.claude/hooks/analyzers/README.md +198 -0
  10. package/templates/.claude/hooks/analyzers/code-quality-checker.ts +154 -0
  11. package/templates/.claude/hooks/analyzers/code-quality.md +54 -0
  12. package/templates/.claude/hooks/analyzers/commit-blocker-example.ts.disabled +110 -0
  13. package/templates/.claude/hooks/analyzers/commit-policy.md +49 -0
  14. package/templates/.claude/hooks/analyzers/event-model-validator.md +49 -0
  15. package/templates/.claude/hooks/analyzers/event-model-validator.ts +169 -0
  16. package/templates/.claude/hooks/analyzers/example-logger.ts +70 -0
  17. package/templates/.claude/hooks/analyzers/slice-scope-validator.md +81 -0
  18. package/templates/.claude/hooks/check-review-result.sh +47 -0
  19. package/templates/.claude/hooks/prepare-review.sh +34 -0
  20. package/templates/.claude/hooks/review-agent-prompt.md +42 -0
  21. package/templates/.claude/hooks/run-review-agent.sh +124 -0
  22. package/templates/.claude/settings.local.json +37 -0
  23. package/templates/.claude/skills/help/README.md +84 -0
  24. package/templates/.claude/skills/help/SKILL.md +393 -0
  25. package/templates/.claude/skills/help/templates/demo-config.json +6753 -0
  26. package/templates/.claude/skills/sample-slices/SKILL.md +8 -0
  27. package/templates/.claude/skills/sample-slices/templates/.slices/Library/addbook/code-slice.json +124 -0
  28. package/templates/.claude/skills/sample-slices/templates/.slices/Library/addbook/slice.json +255 -0
  29. package/templates/.claude/skills/sample-slices/templates/.slices/Library/availablebooks/slice.json +107 -0
  30. package/templates/.claude/skills/sample-slices/templates/.slices/index.json +20 -0
  31. package/templates/.claude/skills/sample-slices/templates/Cart/additem/slice.json +979 -0
  32. package/templates/.claude/skills/sample-slices/templates/Cart/archiveitem/slice.json +529 -0
  33. package/templates/.claude/skills/sample-slices/templates/Cart/cartitems/slice.json +1072 -0
  34. package/templates/.claude/skills/sample-slices/templates/Cart/cartwithproducts/slice.json +394 -0
  35. package/templates/.claude/skills/sample-slices/templates/Cart/changedprices/slice.json +88 -0
  36. package/templates/.claude/skills/sample-slices/templates/Cart/changeinventory/slice.json +264 -0
  37. package/templates/.claude/skills/sample-slices/templates/Cart/changeprice/slice.json +308 -0
  38. package/templates/.claude/skills/sample-slices/templates/Cart/clearcart/slice.json +358 -0
  39. package/templates/.claude/skills/sample-slices/templates/Cart/inventories/slice.json +203 -0
  40. package/templates/.claude/skills/sample-slices/templates/Cart/publishcart/slice.json +876 -0
  41. package/templates/.claude/skills/sample-slices/templates/Cart/removeitem/slice.json +560 -0
  42. package/templates/.claude/skills/sample-slices/templates/Cart/submitcart/slice.json +708 -0
  43. package/templates/.claude/skills/sample-slices/templates/Cart/submittedcartdata/slice.json +399 -0
  44. package/templates/.claude/skills/sample-slices/templates/index.json +108 -0
  45. package/templates/.claude/skills/slice-automation/SKILL.md +49 -0
  46. package/templates/.claude/skills/slice-state-change/SKILL.md +369 -0
  47. package/templates/.claude/skills/slice-state-change/templates/AddLocation/AddLocation.test.ts.sample +76 -0
  48. package/templates/.claude/skills/slice-state-change/templates/AddLocation/AddLocationCommand.ts.sample +84 -0
  49. package/templates/.claude/skills/slice-state-change/templates/AddLocation/routes.ts.sample +73 -0
  50. package/templates/.claude/skills/slice-state-change/templates/README.md +46 -0
  51. package/templates/.claude/skills/slice-state-view/SKILL.md +336 -0
  52. package/templates/.claude/skills/slice-state-view/templates/Locations/Locations.test.ts.sample +84 -0
  53. package/templates/.claude/skills/slice-state-view/templates/Locations/LocationsProjection.ts.sample +50 -0
  54. package/templates/.claude/skills/slice-state-view/templates/Locations/routes.ts.sample +46 -0
  55. package/templates/.claude/skills/slice-state-view/templates/README.md +109 -0
  56. package/templates/.claude/skills/slice-state-view/templates/Tables/Tables.test.ts.sample +104 -0
  57. package/templates/.claude/skills/slice-state-view/templates/Tables/TablesProjection.ts.sample +59 -0
  58. package/templates/.claude/skills/slice-state-view/templates/Tables/routes.ts.sample +46 -0
  59. package/templates/.claude/skills/slice-state-view/templates/V2__tables.sql +7 -0
  60. package/templates/.claude/skills/slice-state-view/templates/V8__locations.sql +7 -0
  61. package/templates/.claude/skills/test-analyzer/SKILL.md +373 -0
  62. package/templates/.claude/skills/test-analyzer/examples/specification-format.md +143 -0
  63. package/templates/.claude/skills/test-analyzer/examples/state-change-example.md +111 -0
  64. package/templates/.claude/skills/test-analyzer/examples/state-view-example.md +122 -0
  65. package/templates/AGENTS.md +110 -0
  66. package/templates/Claude.md +58 -0
  67. package/templates/README.md +178 -0
  68. package/templates/backend/.env +9 -0
  69. package/templates/backend/BACKEND_AUTH_SETUP.md +183 -0
  70. package/templates/backend/SWAGGER.md +213 -0
  71. package/templates/backend/eslint.config.mjs +31 -0
  72. package/templates/backend/flyway.conf +17 -0
  73. package/templates/backend/package.json +44 -0
  74. package/templates/backend/prd.json.example +64 -0
  75. package/templates/backend/public/assets/images/banner.png +0 -0
  76. package/templates/backend/public/assets/logo.png +0 -0
  77. package/templates/backend/public/file.svg +4 -0
  78. package/templates/backend/public/globe.svg +12 -0
  79. package/templates/backend/public/next.svg +6 -0
  80. package/templates/backend/public/vercel.svg +3 -0
  81. package/templates/backend/public/window.svg +5 -0
  82. package/templates/backend/server.ts +129 -0
  83. package/templates/backend/setup-env.sh +50 -0
  84. package/templates/backend/src/common/assertions.ts +6 -0
  85. package/templates/backend/src/common/db.ts +1 -0
  86. package/templates/backend/src/common/loadPostgresEventstore.ts +16 -0
  87. package/templates/backend/src/common/parseEndpoint.ts +51 -0
  88. package/templates/backend/src/common/replay.ts +9 -0
  89. package/templates/backend/src/common/routes.ts +19 -0
  90. package/templates/backend/src/common/testHelpers.ts +53 -0
  91. package/templates/backend/src/core/readmodel.ts +28 -0
  92. package/templates/backend/src/core/types.ts +26 -0
  93. package/templates/backend/src/process/process.ts +53 -0
  94. package/templates/backend/src/supabase/LoginHandler.ts +36 -0
  95. package/templates/backend/src/supabase/ProtectedPageProps.ts +21 -0
  96. package/templates/backend/src/supabase/README.md +171 -0
  97. package/templates/backend/src/supabase/api.ts +63 -0
  98. package/templates/backend/src/supabase/authMiddleware.ts +53 -0
  99. package/templates/backend/src/supabase/component.ts +12 -0
  100. package/templates/backend/src/supabase/requireUser.ts +72 -0
  101. package/templates/backend/src/supabase/serverProps.ts +25 -0
  102. package/templates/backend/src/supabase/staticProps.ts +10 -0
  103. package/templates/backend/src/swagger.ts +34 -0
  104. package/templates/backend/src/util/assertions.ts +6 -0
  105. package/templates/backend/supabase/config.toml +295 -0
  106. package/templates/backend/supabase/migrations/20260121155918593_catalogentries.sql.sample +23 -0
  107. package/templates/backend/supabase/seed.sql +1 -0
  108. package/templates/backend/tsconfig.json +31 -0
  109. package/templates/frontend/.env.development +3 -0
  110. package/templates/frontend/AGENTS.md +7 -0
  111. package/templates/frontend/README.md +73 -0
  112. package/templates/frontend/components.json +20 -0
  113. package/templates/frontend/eslint.config.js +26 -0
  114. package/templates/frontend/index.html +18 -0
  115. package/templates/frontend/package-lock.json +8347 -0
  116. package/templates/frontend/package.json +94 -0
  117. package/templates/frontend/postcss.config.js +6 -0
  118. package/templates/frontend/public/favicon.ico +0 -0
  119. package/templates/frontend/public/logo.png +0 -0
  120. package/templates/frontend/public/placeholder.svg +1 -0
  121. package/templates/frontend/public/robots.txt +14 -0
  122. package/templates/frontend/src/App.css +42 -0
  123. package/templates/frontend/src/App.tsx +47 -0
  124. package/templates/frontend/src/components/NavLink.tsx +28 -0
  125. package/templates/frontend/src/components/ProtectedRoute.tsx +24 -0
  126. package/templates/frontend/src/components/calendar/Calendar.tsx +302 -0
  127. package/templates/frontend/src/components/layout/DashboardLayout.tsx +21 -0
  128. package/templates/frontend/src/components/layout/Header.tsx +45 -0
  129. package/templates/frontend/src/components/layout/Sidebar.tsx +82 -0
  130. package/templates/frontend/src/components/tables/ReservationTemplates.tsx +189 -0
  131. package/templates/frontend/src/components/ui/accordion.tsx +52 -0
  132. package/templates/frontend/src/components/ui/alert-dialog.tsx +104 -0
  133. package/templates/frontend/src/components/ui/alert.tsx +43 -0
  134. package/templates/frontend/src/components/ui/aspect-ratio.tsx +5 -0
  135. package/templates/frontend/src/components/ui/avatar.tsx +38 -0
  136. package/templates/frontend/src/components/ui/badge.tsx +29 -0
  137. package/templates/frontend/src/components/ui/breadcrumb.tsx +90 -0
  138. package/templates/frontend/src/components/ui/button.tsx +47 -0
  139. package/templates/frontend/src/components/ui/calendar.tsx +54 -0
  140. package/templates/frontend/src/components/ui/card.tsx +43 -0
  141. package/templates/frontend/src/components/ui/carousel.tsx +224 -0
  142. package/templates/frontend/src/components/ui/chart.tsx +303 -0
  143. package/templates/frontend/src/components/ui/checkbox.tsx +26 -0
  144. package/templates/frontend/src/components/ui/collapsible.tsx +9 -0
  145. package/templates/frontend/src/components/ui/command.tsx +132 -0
  146. package/templates/frontend/src/components/ui/context-menu.tsx +178 -0
  147. package/templates/frontend/src/components/ui/dialog.tsx +95 -0
  148. package/templates/frontend/src/components/ui/drawer.tsx +87 -0
  149. package/templates/frontend/src/components/ui/dropdown-menu.tsx +179 -0
  150. package/templates/frontend/src/components/ui/form.tsx +129 -0
  151. package/templates/frontend/src/components/ui/hover-card.tsx +27 -0
  152. package/templates/frontend/src/components/ui/input-otp.tsx +61 -0
  153. package/templates/frontend/src/components/ui/input.tsx +22 -0
  154. package/templates/frontend/src/components/ui/label.tsx +17 -0
  155. package/templates/frontend/src/components/ui/menubar.tsx +207 -0
  156. package/templates/frontend/src/components/ui/navigation-menu.tsx +120 -0
  157. package/templates/frontend/src/components/ui/pagination.tsx +81 -0
  158. package/templates/frontend/src/components/ui/popover.tsx +29 -0
  159. package/templates/frontend/src/components/ui/progress.tsx +23 -0
  160. package/templates/frontend/src/components/ui/radio-group.tsx +36 -0
  161. package/templates/frontend/src/components/ui/resizable.tsx +37 -0
  162. package/templates/frontend/src/components/ui/scroll-area.tsx +38 -0
  163. package/templates/frontend/src/components/ui/select.tsx +143 -0
  164. package/templates/frontend/src/components/ui/separator.tsx +20 -0
  165. package/templates/frontend/src/components/ui/sheet.tsx +107 -0
  166. package/templates/frontend/src/components/ui/sidebar.tsx +637 -0
  167. package/templates/frontend/src/components/ui/skeleton.tsx +7 -0
  168. package/templates/frontend/src/components/ui/slider.tsx +23 -0
  169. package/templates/frontend/src/components/ui/sonner.tsx +27 -0
  170. package/templates/frontend/src/components/ui/stat-card.tsx +44 -0
  171. package/templates/frontend/src/components/ui/switch.tsx +27 -0
  172. package/templates/frontend/src/components/ui/table.tsx +72 -0
  173. package/templates/frontend/src/components/ui/tabs.tsx +53 -0
  174. package/templates/frontend/src/components/ui/textarea.tsx +21 -0
  175. package/templates/frontend/src/components/ui/toast.tsx +111 -0
  176. package/templates/frontend/src/components/ui/toaster.tsx +24 -0
  177. package/templates/frontend/src/components/ui/toggle-group.tsx +49 -0
  178. package/templates/frontend/src/components/ui/toggle.tsx +37 -0
  179. package/templates/frontend/src/components/ui/tooltip.tsx +28 -0
  180. package/templates/frontend/src/components/ui/use-toast.ts +3 -0
  181. package/templates/frontend/src/contexts/AuthContext.tsx +94 -0
  182. package/templates/frontend/src/contexts/RefreshContext.tsx +236 -0
  183. package/templates/frontend/src/hooks/api/index.ts +2 -0
  184. package/templates/frontend/src/hooks/api/useLocations.ts +15 -0
  185. package/templates/frontend/src/hooks/use-mobile.tsx +19 -0
  186. package/templates/frontend/src/hooks/use-toast.ts +186 -0
  187. package/templates/frontend/src/hooks/useApiContext.ts +11 -0
  188. package/templates/frontend/src/index.css +118 -0
  189. package/templates/frontend/src/integrations/supabase/client.ts +9 -0
  190. package/templates/frontend/src/lib/api-client.ts +136 -0
  191. package/templates/frontend/src/lib/api.ts +1028 -0
  192. package/templates/frontend/src/lib/utils.ts +6 -0
  193. package/templates/frontend/src/main.tsx +5 -0
  194. package/templates/frontend/src/pages/Auth.tsx +408 -0
  195. package/templates/frontend/src/pages/Dashboard.tsx +168 -0
  196. package/templates/frontend/src/pages/Menus.tsx +224 -0
  197. package/templates/frontend/src/pages/NotFound.tsx +24 -0
  198. package/templates/frontend/src/pages/Register.tsx +285 -0
  199. package/templates/frontend/src/test/example.test.ts +0 -0
  200. package/templates/frontend/src/test/setup.ts +15 -0
  201. package/templates/frontend/src/types/index.ts +8 -0
  202. package/templates/frontend/src/vite-env.d.ts +1 -0
  203. package/templates/frontend/tailwind.config.ts +101 -0
  204. package/templates/frontend/tsconfig.app.json +31 -0
  205. package/templates/frontend/tsconfig.json +16 -0
  206. package/templates/frontend/tsconfig.node.json +22 -0
  207. package/templates/frontend/vite.config.ts +21 -0
  208. package/templates/frontend/vitest.config.ts +16 -0
  209. package/templates/init.sh +1 -0
  210. package/templates/prompt.md +139 -0
  211. package/templates/ralph.sh +120 -0
  212. package/templates/server.mjs +505 -0
@@ -0,0 +1,94 @@
1
+ {
2
+ "name": "vite_react_shadcn_ts",
3
+ "private": true,
4
+ "version": "0.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "build:dev": "vite build --mode development",
10
+ "lint": "eslint .",
11
+ "preview": "vite preview",
12
+ "test": "vitest run",
13
+ "test:watch": "vitest"
14
+ },
15
+ "dependencies": {
16
+ "@toast-ui/react-calendar": "^2.1.3",
17
+ "@hookform/resolvers": "^3.10.0",
18
+ "@radix-ui/react-accordion": "^1.2.11",
19
+ "@radix-ui/react-alert-dialog": "^1.1.14",
20
+ "@radix-ui/react-aspect-ratio": "^1.1.7",
21
+ "@radix-ui/react-avatar": "^1.1.10",
22
+ "@radix-ui/react-checkbox": "^1.3.2",
23
+ "@radix-ui/react-collapsible": "^1.1.11",
24
+ "@radix-ui/react-context-menu": "^2.2.15",
25
+ "@radix-ui/react-dialog": "^1.1.14",
26
+ "@radix-ui/react-dropdown-menu": "^2.1.15",
27
+ "@radix-ui/react-hover-card": "^1.1.14",
28
+ "@radix-ui/react-label": "^2.1.7",
29
+ "@radix-ui/react-menubar": "^1.1.15",
30
+ "@radix-ui/react-navigation-menu": "^1.2.13",
31
+ "@radix-ui/react-popover": "^1.1.14",
32
+ "@radix-ui/react-progress": "^1.1.7",
33
+ "@radix-ui/react-radio-group": "^1.3.7",
34
+ "@radix-ui/react-scroll-area": "^1.2.9",
35
+ "@radix-ui/react-select": "^2.2.5",
36
+ "@radix-ui/react-separator": "^1.1.7",
37
+ "@radix-ui/react-slider": "^1.3.5",
38
+ "@radix-ui/react-slot": "^1.2.3",
39
+ "@radix-ui/react-switch": "^1.2.5",
40
+ "@radix-ui/react-tabs": "^1.1.12",
41
+ "@radix-ui/react-toast": "^1.2.14",
42
+ "@radix-ui/react-toggle": "^1.1.9",
43
+ "@radix-ui/react-toggle-group": "^1.1.10",
44
+ "@radix-ui/react-tooltip": "^1.2.7",
45
+ "@supabase/supabase-js": "^2.75.0",
46
+ "@tanstack/react-query": "^5.83.0",
47
+ "class-variance-authority": "^0.7.1",
48
+ "clsx": "^2.1.1",
49
+ "cmdk": "^1.1.1",
50
+ "date-fns": "^3.6.0",
51
+ "embla-carousel-react": "^8.6.0",
52
+ "input-otp": "^1.4.2",
53
+ "lucide-react": "^0.462.0",
54
+ "next-themes": "^0.3.0",
55
+ "react": "^18.3.1",
56
+ "react-calendar": "^6.0.0",
57
+ "react-day-picker": "^8.10.1",
58
+ "react-dom": "^18.3.1",
59
+ "react-hook-form": "^7.61.1",
60
+ "react-resizable-panels": "^2.1.9",
61
+ "react-router-dom": "^6.30.1",
62
+ "recharts": "^2.15.4",
63
+ "sonner": "^1.7.4",
64
+ "tailwind-merge": "^2.6.0",
65
+ "tailwindcss-animate": "^1.0.7",
66
+ "toast-ui": "^1.0.0",
67
+ "uuid": "^13.0.0",
68
+ "vaul": "^0.9.9",
69
+ "zod": "^3.25.76"
70
+ },
71
+ "devDependencies": {
72
+ "@eslint/js": "^9.32.0",
73
+ "@tailwindcss/typography": "^0.5.16",
74
+ "@testing-library/jest-dom": "^6.6.0",
75
+ "@testing-library/react": "^16.0.0",
76
+ "@types/node": "^22.16.5",
77
+ "@types/react": "^18.3.23",
78
+ "@types/react-dom": "^18.3.7",
79
+ "@vitejs/plugin-react-swc": "^3.11.0",
80
+ "autoprefixer": "^10.4.21",
81
+ "eslint": "^9.32.0",
82
+ "eslint-plugin-react-hooks": "^5.2.0",
83
+ "eslint-plugin-react-refresh": "^0.4.20",
84
+ "globals": "^15.15.0",
85
+ "jsdom": "^20.0.3",
86
+ "lovable-tagger": "^1.1.13",
87
+ "postcss": "^8.5.6",
88
+ "tailwindcss": "^3.4.17",
89
+ "typescript": "^5.8.3",
90
+ "typescript-eslint": "^8.38.0",
91
+ "vite": "^5.4.19",
92
+ "vitest": "^3.2.4"
93
+ }
94
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ };
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="1200" height="1200" fill="none"><rect width="1200" height="1200" fill="#EAEAEA" rx="3"/><g opacity=".5"><g opacity=".5"><path fill="#FAFAFA" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 736.5c-75.454 0-136.621-61.167-136.621-136.62 0-75.454 61.167-136.621 136.621-136.621 75.453 0 136.62 61.167 136.62 136.621 0 75.453-61.167 136.62-136.62 136.62Z"/></g><path stroke="url(#a)" stroke-width="2.418" d="M0-1.209h553.581" transform="scale(1 -1) rotate(45 1163.11 91.165)"/><path stroke="url(#b)" stroke-width="2.418" d="M404.846 598.671h391.726"/><path stroke="url(#c)" stroke-width="2.418" d="M599.5 795.742V404.017"/><path stroke="url(#d)" stroke-width="2.418" d="m795.717 796.597-391.441-391.44"/><path fill="#fff" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/><g clip-path="url(#e)"><path fill="#666" fill-rule="evenodd" d="M616.426 586.58h-31.434v16.176l3.553-3.554.531-.531h9.068l.074-.074 8.463-8.463h2.565l7.18 7.181V586.58Zm-15.715 14.654 3.698 3.699 1.283 1.282-2.565 2.565-1.282-1.283-5.2-5.199h-6.066l-5.514 5.514-.073.073v2.876a2.418 2.418 0 0 0 2.418 2.418h26.598a2.418 2.418 0 0 0 2.418-2.418v-8.317l-8.463-8.463-7.181 7.181-.071.072Zm-19.347 5.442v4.085a6.045 6.045 0 0 0 6.046 6.045h26.598a6.044 6.044 0 0 0 6.045-6.045v-7.108l1.356-1.355-1.282-1.283-.074-.073v-17.989h-38.689v23.43l-.146.146.146.147Z" clip-rule="evenodd"/></g><path stroke="#C9C9C9" stroke-width="2.418" d="M600.709 656.704c-31.384 0-56.825-25.441-56.825-56.824 0-31.384 25.441-56.825 56.825-56.825 31.383 0 56.824 25.441 56.824 56.825 0 31.383-25.441 56.824-56.824 56.824Z"/></g><defs><linearGradient id="a" x1="554.061" x2="-.48" y1=".083" y2=".087" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="b" x1="796.912" x2="404.507" y1="599.963" y2="599.965" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="c" x1="600.792" x2="600.794" y1="403.677" y2="796.082" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><linearGradient id="d" x1="404.85" x2="796.972" y1="403.903" y2="796.02" gradientUnits="userSpaceOnUse"><stop stop-color="#C9C9C9" stop-opacity="0"/><stop offset=".208" stop-color="#C9C9C9"/><stop offset=".792" stop-color="#C9C9C9"/><stop offset="1" stop-color="#C9C9C9" stop-opacity="0"/></linearGradient><clipPath id="e"><path fill="#fff" d="M581.364 580.535h38.689v38.689h-38.689z"/></clipPath></defs></svg>
@@ -0,0 +1,14 @@
1
+ User-agent: Googlebot
2
+ Allow: /
3
+
4
+ User-agent: Bingbot
5
+ Allow: /
6
+
7
+ User-agent: Twitterbot
8
+ Allow: /
9
+
10
+ User-agent: facebookexternalhit
11
+ Allow: /
12
+
13
+ User-agent: *
14
+ Allow: /
@@ -0,0 +1,42 @@
1
+ #root {
2
+ max-width: 1280px;
3
+ margin: 0 auto;
4
+ padding: 2rem;
5
+ text-align: center;
6
+ }
7
+
8
+ .logo {
9
+ height: 6em;
10
+ padding: 1.5em;
11
+ will-change: filter;
12
+ transition: filter 300ms;
13
+ }
14
+ .logo:hover {
15
+ filter: drop-shadow(0 0 2em #646cffaa);
16
+ }
17
+ .logo.react:hover {
18
+ filter: drop-shadow(0 0 2em #61dafbaa);
19
+ }
20
+
21
+ @keyframes logo-spin {
22
+ from {
23
+ transform: rotate(0deg);
24
+ }
25
+ to {
26
+ transform: rotate(360deg);
27
+ }
28
+ }
29
+
30
+ @media (prefers-reduced-motion: no-preference) {
31
+ a:nth-of-type(2) .logo {
32
+ animation: logo-spin infinite 20s linear;
33
+ }
34
+ }
35
+
36
+ .card {
37
+ padding: 2em;
38
+ }
39
+
40
+ .read-the-docs {
41
+ color: #888;
42
+ }
@@ -0,0 +1,47 @@
1
+ import {Toaster} from "@/components/ui/toaster";
2
+ import {Toaster as Sonner} from "@/components/ui/sonner";
3
+ import {TooltipProvider} from "@/components/ui/tooltip";
4
+ import {AuthProvider} from "@/contexts/AuthContext";
5
+
6
+ import {QueryClient, QueryClientProvider} from "@tanstack/react-query";
7
+ import {BrowserRouter, Routes, Route} from "react-router-dom";
8
+ import Dashboard from "./pages/Dashboard";
9
+ import Tables from "./pages/Tables";
10
+ import Staff from "./pages/Staff";
11
+ import Shifts from "./pages/Shifts";
12
+ import Tasks from "./pages/Tasks";
13
+ import Menus from "./pages/Menus";
14
+ import Vacations from "./pages/Vacations";
15
+ import NotFound from "./pages/NotFound";
16
+ import Auth from "@/pages/Auth.tsx";
17
+ import Register from "@/pages/Register.tsx";
18
+ import { ProtectedRoute } from "./components/ProtectedRoute";
19
+
20
+ const queryClient = new QueryClient();
21
+
22
+ const App = () => (
23
+ <QueryClientProvider client={queryClient}>
24
+ <AuthProvider>
25
+ <TooltipProvider>
26
+ <Toaster/>
27
+ <Sonner/>
28
+ <BrowserRouter>
29
+ <Routes>
30
+ <Route path="/register" element={<Register/>}/>
31
+ <Route path="/auth" element={<Auth/>}/>
32
+ <Route path="/" element={<ProtectedRoute><Dashboard/></ProtectedRoute>}/>
33
+ <Route path="/tables" element={<ProtectedRoute><Tables/></ProtectedRoute>}/>
34
+ <Route path="/staff" element={<ProtectedRoute><Staff/></ProtectedRoute>}/>
35
+ <Route path="/shifts" element={<ProtectedRoute><Shifts/></ProtectedRoute>}/>
36
+ <Route path="/tasks" element={<ProtectedRoute><Tasks/></ProtectedRoute>}/>
37
+ <Route path="/menus" element={<ProtectedRoute><Menus/></ProtectedRoute>}/>
38
+ <Route path="/vacations" element={<ProtectedRoute><Vacations/></ProtectedRoute>}/>
39
+ <Route path="*" element={<NotFound/>}/>
40
+ </Routes>
41
+ </BrowserRouter>
42
+ </TooltipProvider>
43
+ </AuthProvider>
44
+ </QueryClientProvider>
45
+ );
46
+
47
+ export default App;
@@ -0,0 +1,28 @@
1
+ import { NavLink as RouterNavLink, NavLinkProps } from "react-router-dom";
2
+ import { forwardRef } from "react";
3
+ import { cn } from "@/lib/utils";
4
+
5
+ interface NavLinkCompatProps extends Omit<NavLinkProps, "className"> {
6
+ className?: string;
7
+ activeClassName?: string;
8
+ pendingClassName?: string;
9
+ }
10
+
11
+ const NavLink = forwardRef<HTMLAnchorElement, NavLinkCompatProps>(
12
+ ({ className, activeClassName, pendingClassName, to, ...props }, ref) => {
13
+ return (
14
+ <RouterNavLink
15
+ ref={ref}
16
+ to={to}
17
+ className={({ isActive, isPending }) =>
18
+ cn(className, isActive && activeClassName, isPending && pendingClassName)
19
+ }
20
+ {...props}
21
+ />
22
+ );
23
+ },
24
+ );
25
+
26
+ NavLink.displayName = "NavLink";
27
+
28
+ export { NavLink };
@@ -0,0 +1,24 @@
1
+ import { Navigate } from 'react-router-dom';
2
+ import { useAuth } from '@/contexts/AuthContext';
3
+
4
+ interface ProtectedRouteProps {
5
+ children: React.ReactNode;
6
+ }
7
+
8
+ export function ProtectedRoute({ children }: ProtectedRouteProps) {
9
+ const { user, loading, tenantId } = useAuth();
10
+
11
+ if (loading) {
12
+ return (
13
+ <div className="min-h-screen flex items-center justify-center bg-background">
14
+ <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary"></div>
15
+ </div>
16
+ );
17
+ }
18
+
19
+ if (!user || !tenantId) {
20
+ return <Navigate to="/auth" replace />;
21
+ }
22
+
23
+ return <>{children}</>;
24
+ }
@@ -0,0 +1,302 @@
1
+ import {useMemo, useRef, useCallback} from "react";
2
+ import {Card, CardContent, CardHeader, CardTitle} from "@/components/ui/card";
3
+ import {Button} from "@/components/ui/button";
4
+ import {Calendar as CalendarIcon, ChevronLeft, ChevronRight, Phone, Mail, Clock, AlignLeft} from "lucide-react";
5
+ import Calendar from "@toast-ui/react-calendar";
6
+ import "@toast-ui/calendar/dist/toastui-calendar.min.css";
7
+ import {useState} from "react";
8
+ import {
9
+ Dialog,
10
+ DialogContent,
11
+ DialogHeader,
12
+ DialogTitle,
13
+ } from "@/components/ui/dialog";
14
+ import {format} from "date-fns";
15
+ import {de} from "date-fns/locale";
16
+
17
+
18
+ export type CalendarEntry = {
19
+ type: string,
20
+ id: string,
21
+ title: string,
22
+ description: string,
23
+ phone: string,
24
+ email: string,
25
+ start: string,
26
+ end: string,
27
+ showupRegistered?: boolean,
28
+ }
29
+
30
+ interface DashboardCalendarProps {
31
+ entries: CalendarEntry[];
32
+ onAppointmentClick: (appointment: CalendarEntry) => void;
33
+ onNoShow?: (reservationId: string) => void;
34
+ onShowUp?: (reservationId: string) => void;
35
+ }
36
+
37
+ export function DashboardCalendar({
38
+ entries,
39
+ onAppointmentClick,
40
+ onNoShow,
41
+ onShowUp,
42
+ }: DashboardCalendarProps) {
43
+ const calendarRef = useRef<typeof Calendar>(null);
44
+ const [currentDate, setCurrentDate] = useState(new Date());
45
+ const [view, setView] = useState<'month' | 'week' | 'day'>('month');
46
+ const [selectedEntry, setSelectedEntry] = useState<CalendarEntry | null>(null);
47
+
48
+ // Convert appointments to Toast UI Calendar events
49
+ const events = useMemo(() => {
50
+ return entries.map(entry => {
51
+ const showup = entry.showupRegistered === true;
52
+ return {
53
+ id: entry.id,
54
+ calendarId: 'reservations',
55
+ title: entry.title,
56
+ body: entry.description || '',
57
+ email: entry.email,
58
+ phone: entry.phone,
59
+ start: new Date(entry.start),
60
+ end: new Date(entry.end),
61
+ category: 'time' as const,
62
+ backgroundColor: showup ? '#16a34a' : 'hsl(var(--primary))',
63
+ color: '#ffffff',
64
+ borderColor: showup ? '#15803d' : 'hsl(var(--primary))',
65
+ raw: entry,
66
+ };
67
+ });
68
+ }, [entries]);
69
+
70
+ const calendars = useMemo(() => [
71
+ {
72
+ id: 'reservations',
73
+ name: 'Appointments',
74
+ backgroundColor: 'hsl(var(--primary))',
75
+ borderColor: 'hsl(var(--primary))',
76
+ color: 'hsl(var(--primary-foreground))',
77
+ },
78
+ ], []);
79
+
80
+ const handleClickEvent = useCallback((eventInfo: { event: { raw: CalendarEntry } }) => {
81
+ if (eventInfo.event.raw) {
82
+ setSelectedEntry(eventInfo.event.raw);
83
+ onAppointmentClick(eventInfo.event.raw);
84
+ }
85
+ }, [onAppointmentClick]);
86
+
87
+ const handlePrev = () => {
88
+ const calendarInstance = (calendarRef.current as any)?.getInstance?.();
89
+ if (calendarInstance) {
90
+ calendarInstance.prev();
91
+ setCurrentDate(calendarInstance.getDate().toDate());
92
+ }
93
+ };
94
+
95
+ const handleNext = () => {
96
+ const calendarInstance = (calendarRef.current as any)?.getInstance?.();
97
+ if (calendarInstance) {
98
+ calendarInstance.next();
99
+ setCurrentDate(calendarInstance.getDate().toDate());
100
+ }
101
+ };
102
+
103
+ const handleToday = () => {
104
+ const calendarInstance = (calendarRef.current as any)?.getInstance?.();
105
+ if (calendarInstance) {
106
+ calendarInstance.today();
107
+ setCurrentDate(calendarInstance.getDate().toDate());
108
+ }
109
+ };
110
+
111
+ const handleViewChange = (newView: 'month' | 'week' | 'day') => {
112
+ setView(newView);
113
+ const calendarInstance = (calendarRef.current as any)?.getInstance?.();
114
+ if (calendarInstance) {
115
+ calendarInstance.changeView(newView);
116
+ }
117
+ };
118
+
119
+ const formatCurrentDate = () => {
120
+ return currentDate.toLocaleDateString('de-DE', {
121
+ month: 'long',
122
+ year: 'numeric'
123
+ });
124
+ };
125
+
126
+ const formatDateTime = (dateStr: string) => {
127
+ try {
128
+ return format(new Date(dateStr), "dd. MMMM yyyy, HH:mm 'Uhr'", {locale: de});
129
+ } catch {
130
+ return dateStr;
131
+ }
132
+ };
133
+
134
+ return (
135
+ <>
136
+ <Card className="mt-6">
137
+ <CardHeader>
138
+ <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
139
+ <CardTitle className="flex items-center gap-2">
140
+ <CalendarIcon className="h-5 w-5"/>
141
+ Kalender
142
+ </CardTitle>
143
+
144
+ <div className="flex items-center gap-2">
145
+ {/* View Toggle */}
146
+ <div className="flex rounded-md border overflow-hidden">
147
+ <Button
148
+ variant={view === 'month' ? 'default' : 'ghost'}
149
+ size="sm"
150
+ onClick={() => handleViewChange('month')}
151
+ className="rounded-none"
152
+ >
153
+ Monat
154
+ </Button>
155
+ <Button
156
+ variant={view === 'week' ? 'default' : 'ghost'}
157
+ size="sm"
158
+ onClick={() => handleViewChange('week')}
159
+ className="rounded-none border-x"
160
+ >
161
+ Woche
162
+ </Button>
163
+ <Button
164
+ variant={view === 'day' ? 'default' : 'ghost'}
165
+ size="sm"
166
+ onClick={() => handleViewChange('day')}
167
+ className="rounded-none"
168
+ >
169
+ Tag
170
+ </Button>
171
+ </div>
172
+
173
+ {/* Navigation */}
174
+ <div className="flex items-center gap-1">
175
+ <Button variant="outline" size="icon" onClick={handlePrev}>
176
+ <ChevronLeft className="h-4 w-4"/>
177
+ </Button>
178
+ <Button variant="outline" size="sm" onClick={handleToday}>
179
+ Heute
180
+ </Button>
181
+ <Button variant="outline" size="icon" onClick={handleNext}>
182
+ <ChevronRight className="h-4 w-4"/>
183
+ </Button>
184
+ </div>
185
+ </div>
186
+ </div>
187
+ <p className="text-lg font-semibold mt-2">{formatCurrentDate()}</p>
188
+ </CardHeader>
189
+ <CardContent>
190
+ <div className="calendar-wrapper" style={{height: '600px'}}>
191
+ <Calendar
192
+ ref={calendarRef}
193
+ height="100%"
194
+ view={view}
195
+ calendars={calendars}
196
+ events={events}
197
+ month={{
198
+ dayNames: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
199
+ startDayOfWeek: 1,
200
+ }}
201
+ week={{
202
+ dayNames: ['So', 'Mo', 'Di', 'Mi', 'Do', 'Fr', 'Sa'],
203
+ startDayOfWeek: 1,
204
+ hourStart: 6,
205
+ hourEnd: 22,
206
+ taskView: false,
207
+ eventView: ['time'],
208
+ }}
209
+ usageStatistics={false}
210
+ isReadOnly={true}
211
+ onClickEvent={handleClickEvent}
212
+ template={{
213
+ time(event: { title: string, body: string, raw: { email: string, phone: string } }) {
214
+ const emailIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:inline;vertical-align:middle;margin-right:3px"><rect width="20" height="16" x="2" y="4" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></svg>`;
215
+ const phoneIcon = `<svg xmlns="http://www.w3.org/2000/svg" width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" style="display:inline;vertical-align:middle;margin-right:3px"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.69 12 19.79 19.79 0 0 1 1.61 3.4 2 2 0 0 1 3.6 1.22h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L7.91 8.78a16 16 0 0 0 6.29 6.29l.96-.96a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 16.92z"/></svg>`;
216
+ return `<span style="font-size: 12px;">
217
+ <div><b>${event.title}</b></div><br/>
218
+ <div>Anlass / Beschreibung: ${event.body}</div>
219
+ ${event.raw?.email ? `<div>${emailIcon}${event.raw.email}</div>` : ''}
220
+ ${event.raw?.phone ? `<div>${phoneIcon}${event.raw.phone}</div>` : ''}
221
+ </span>`;
222
+ },
223
+ allday(event: { title: string }) {
224
+ return `<span style="font-size: 12px;">${event.title}</span>`;
225
+ },
226
+ timegridDisplayPrimaryTime({time}: { time: Date }) {
227
+ return `${time.getHours().toString().padStart(2, '0')}:${time.getMinutes().toString().padStart(2, '0')}`;
228
+ },
229
+ timegridDisplayTime({time}: { time: Date }) {
230
+ return `${time.getHours().toString().padStart(2, '0')}:${time.getMinutes().toString().padStart(2, '0')}`;
231
+ },
232
+ }}
233
+ />
234
+ </div>
235
+ </CardContent>
236
+ </Card>
237
+
238
+ <Dialog open={!!selectedEntry} onOpenChange={(open) => !open && setSelectedEntry(null)}>
239
+ <DialogContent>
240
+ <DialogHeader>
241
+ <DialogTitle>{selectedEntry?.title}</DialogTitle>
242
+ </DialogHeader>
243
+ {selectedEntry && (
244
+ <div className="space-y-4 pt-2">
245
+ <div className="flex items-start gap-3">
246
+ <Clock className="h-4 w-4 mt-0.5 text-muted-foreground shrink-0"/>
247
+ <div className="text-sm">
248
+ <p>{formatDateTime(selectedEntry.start)}</p>
249
+ <p className="text-muted-foreground">bis {formatDateTime(selectedEntry.end)}</p>
250
+ </div>
251
+ </div>
252
+ {selectedEntry.description && (
253
+ <div className="flex items-start gap-3">
254
+ <AlignLeft className="h-4 w-4 mt-0.5 text-muted-foreground shrink-0"/>
255
+ <p className="text-sm">{selectedEntry.description}</p>
256
+ </div>
257
+ )}
258
+ {selectedEntry.email && (
259
+ <div className="flex items-center gap-3">
260
+ <Mail className="h-4 w-4 text-muted-foreground shrink-0"/>
261
+ <a href={`mailto:${selectedEntry.email}`}
262
+ className="text-sm text-primary hover:underline">
263
+ {selectedEntry.email}
264
+ </a>
265
+ </div>
266
+ )}
267
+ {selectedEntry.phone && (
268
+ <div className="flex items-center gap-3">
269
+ <Phone className="h-4 w-4 text-muted-foreground shrink-0"/>
270
+ <a href={`tel:${selectedEntry.phone}`}
271
+ className="text-sm text-primary hover:underline">
272
+ {selectedEntry.phone}
273
+ </a>
274
+ </div>
275
+ )}
276
+ <div className="flex gap-3 pt-2">
277
+ <Button
278
+ className="flex-1 bg-green-600 hover:bg-green-700 text-white"
279
+ onClick={() => {
280
+ onShowUp?.(selectedEntry.id);
281
+ setSelectedEntry(null);
282
+ }}
283
+ >
284
+ Sind gekommen
285
+ </Button>
286
+ <Button
287
+ className="flex-1 bg-red-600 hover:bg-red-700 text-white"
288
+ onClick={() => {
289
+ onNoShow?.(selectedEntry.id);
290
+ setSelectedEntry(null);
291
+ }}
292
+ >
293
+ Sind nicht gekommen
294
+ </Button>
295
+ </div>
296
+ </div>
297
+ )}
298
+ </DialogContent>
299
+ </Dialog>
300
+ </>
301
+ );
302
+ }
@@ -0,0 +1,21 @@
1
+ import { ReactNode } from "react";
2
+ import { Sidebar } from "./Sidebar";
3
+ import { Header } from "./Header";
4
+
5
+ interface DashboardLayoutProps {
6
+ children: ReactNode;
7
+ title: string;
8
+ subtitle?: string;
9
+ }
10
+
11
+ export function DashboardLayout({ children, title, subtitle }: DashboardLayoutProps) {
12
+ return (
13
+ <div className="min-h-screen bg-background">
14
+ <Sidebar />
15
+ <div className="pl-64">
16
+ <Header title={title} subtitle={subtitle} />
17
+ <main className="p-6">{children}</main>
18
+ </div>
19
+ </div>
20
+ );
21
+ }
@@ -0,0 +1,45 @@
1
+ import { Bell, Search } from "lucide-react";
2
+ import { Button } from "@/components/ui/button";
3
+ import { Input } from "@/components/ui/input";
4
+
5
+ interface HeaderProps {
6
+ title: string;
7
+ subtitle?: string;
8
+ }
9
+
10
+ export function Header({ title, subtitle }: HeaderProps) {
11
+ return (
12
+ <header className="sticky top-0 z-40 flex h-16 items-center justify-between border-b border-border bg-background/95 px-6 backdrop-blur supports-[backdrop-filter]:bg-background/60">
13
+ <div>
14
+ <h1 className="text-xl font-semibold text-foreground">{title}</h1>
15
+ {subtitle && (
16
+ <p className="text-sm text-muted-foreground">{subtitle}</p>
17
+ )}
18
+ </div>
19
+
20
+ <div className="flex items-center gap-4">
21
+ <div className="relative hidden md:block">
22
+ <Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
23
+ <Input
24
+ type="search"
25
+ placeholder="Search..."
26
+ className="w-64 pl-9"
27
+ />
28
+ </div>
29
+
30
+ <Button variant="ghost" size="icon" className="relative">
31
+ <Bell className="h-5 w-5" />
32
+ <span className="absolute -right-0.5 -top-0.5 flex h-4 w-4 items-center justify-center rounded-full bg-primary text-[10px] font-medium text-primary-foreground">
33
+ 3
34
+ </span>
35
+ </Button>
36
+
37
+ <div className="flex items-center gap-3">
38
+ <div className="h-9 w-9 rounded-full bg-primary/10 flex items-center justify-center">
39
+ <span className="text-sm font-medium text-primary">JD</span>
40
+ </div>
41
+ </div>
42
+ </div>
43
+ </header>
44
+ );
45
+ }