fastapi-fullstack 0.1.7__py3-none-any.whl

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 (241) hide show
  1. fastapi_fullstack-0.1.7.dist-info/METADATA +739 -0
  2. fastapi_fullstack-0.1.7.dist-info/RECORD +241 -0
  3. fastapi_fullstack-0.1.7.dist-info/WHEEL +4 -0
  4. fastapi_fullstack-0.1.7.dist-info/entry_points.txt +2 -0
  5. fastapi_fullstack-0.1.7.dist-info/licenses/LICENSE +21 -0
  6. fastapi_gen/__init__.py +3 -0
  7. fastapi_gen/cli.py +442 -0
  8. fastapi_gen/config.py +356 -0
  9. fastapi_gen/generator.py +207 -0
  10. fastapi_gen/prompts.py +874 -0
  11. fastapi_gen/template/VARIABLES.md +276 -0
  12. fastapi_gen/template/cookiecutter.json +93 -0
  13. fastapi_gen/template/hooks/post_gen_project.py +355 -0
  14. fastapi_gen/template/{{cookiecutter.project_slug}}/.env.prod.example +56 -0
  15. fastapi_gen/template/{{cookiecutter.project_slug}}/.github/workflows/ci.yml +150 -0
  16. fastapi_gen/template/{{cookiecutter.project_slug}}/.gitignore +109 -0
  17. fastapi_gen/template/{{cookiecutter.project_slug}}/AGENTS.md +55 -0
  18. fastapi_gen/template/{{cookiecutter.project_slug}}/CLAUDE.md +99 -0
  19. fastapi_gen/template/{{cookiecutter.project_slug}}/Makefile +315 -0
  20. fastapi_gen/template/{{cookiecutter.project_slug}}/README.md +768 -0
  21. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.dockerignore +60 -0
  22. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.env.example +155 -0
  23. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.pre-commit-config.yaml +32 -0
  24. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/Dockerfile +56 -0
  25. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/env.py +76 -0
  26. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/script.py.mako +30 -0
  27. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/versions/.gitkeep +0 -0
  28. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic.ini +48 -0
  29. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/__init__.py +3 -0
  30. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/admin.py +447 -0
  31. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/__init__.py +23 -0
  32. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/assistant.py +226 -0
  33. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/langchain_assistant.py +226 -0
  34. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/prompts.py +10 -0
  35. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/tools/__init__.py +13 -0
  36. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/tools/datetime_tool.py +17 -0
  37. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/__init__.py +1 -0
  38. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/deps.py +541 -0
  39. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/exception_handlers.py +98 -0
  40. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/router.py +10 -0
  41. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/__init__.py +9 -0
  42. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/__init__.py +87 -0
  43. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/agent.py +902 -0
  44. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/auth.py +395 -0
  45. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/conversations.py +498 -0
  46. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/health.py +227 -0
  47. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/items.py +275 -0
  48. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/oauth.py +205 -0
  49. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/sessions.py +168 -0
  50. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/users.py +333 -0
  51. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/webhooks.py +477 -0
  52. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/ws.py +46 -0
  53. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/versioning.py +221 -0
  54. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/clients/__init__.py +14 -0
  55. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/clients/redis.py +88 -0
  56. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/__init__.py +117 -0
  57. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/cleanup.py +75 -0
  58. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/example.py +28 -0
  59. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/seed.py +266 -0
  60. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/__init__.py +5 -0
  61. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/cache.py +23 -0
  62. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/config.py +267 -0
  63. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/csrf.py +153 -0
  64. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/exceptions.py +122 -0
  65. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/logfire_setup.py +101 -0
  66. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/middleware.py +99 -0
  67. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/oauth.py +23 -0
  68. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/rate_limit.py +58 -0
  69. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/sanitize.py +271 -0
  70. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/security.py +102 -0
  71. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/__init__.py +7 -0
  72. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/base.py +41 -0
  73. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/__init__.py +31 -0
  74. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/conversation.py +319 -0
  75. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/item.py +96 -0
  76. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/session.py +126 -0
  77. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/user.py +218 -0
  78. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/webhook.py +244 -0
  79. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/session.py +130 -0
  80. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/main.py +334 -0
  81. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/pipelines/__init__.py +9 -0
  82. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/pipelines/base.py +73 -0
  83. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/__init__.py +49 -0
  84. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/base.py +154 -0
  85. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/conversation.py +838 -0
  86. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/item.py +222 -0
  87. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/session.py +318 -0
  88. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/user.py +322 -0
  89. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/webhook.py +358 -0
  90. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/__init__.py +50 -0
  91. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/base.py +57 -0
  92. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/conversation.py +192 -0
  93. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/item.py +52 -0
  94. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/session.py +42 -0
  95. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/token.py +31 -0
  96. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/user.py +64 -0
  97. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/webhook.py +89 -0
  98. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/__init__.py +38 -0
  99. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/conversation.py +850 -0
  100. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/item.py +246 -0
  101. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/session.py +333 -0
  102. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/user.py +432 -0
  103. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/webhook.py +561 -0
  104. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/__init__.py +5 -0
  105. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/celery_app.py +64 -0
  106. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/taskiq_app.py +38 -0
  107. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/__init__.py +25 -0
  108. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/examples.py +106 -0
  109. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/schedules.py +29 -0
  110. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/taskiq_examples.py +92 -0
  111. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/cli/__init__.py +1 -0
  112. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/cli/commands.py +438 -0
  113. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/pyproject.toml +180 -0
  114. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/scripts/.gitkeep +0 -0
  115. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/__init__.py +1 -0
  116. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/__init__.py +1 -0
  117. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_auth.py +242 -0
  118. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_exceptions.py +151 -0
  119. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_health.py +113 -0
  120. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_items.py +310 -0
  121. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_users.py +253 -0
  122. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/conftest.py +151 -0
  123. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_admin.py +890 -0
  124. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_agents.py +261 -0
  125. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_clients.py +183 -0
  126. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_commands.py +173 -0
  127. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_core.py +143 -0
  128. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_pipelines.py +118 -0
  129. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_repositories.py +181 -0
  130. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_security.py +124 -0
  131. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_services.py +363 -0
  132. fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_worker.py +85 -0
  133. fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.dev.yml +242 -0
  134. fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.frontend.yml +31 -0
  135. fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.prod.yml +435 -0
  136. fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.yml +241 -0
  137. fastapi_gen/template/{{cookiecutter.project_slug}}/docs/adding_features.md +132 -0
  138. fastapi_gen/template/{{cookiecutter.project_slug}}/docs/architecture.md +63 -0
  139. fastapi_gen/template/{{cookiecutter.project_slug}}/docs/patterns.md +161 -0
  140. fastapi_gen/template/{{cookiecutter.project_slug}}/docs/testing.md +120 -0
  141. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.dockerignore +40 -0
  142. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.env.example +12 -0
  143. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.gitignore +45 -0
  144. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.prettierignore +19 -0
  145. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.prettierrc +11 -0
  146. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/Dockerfile +44 -0
  147. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/README.md +648 -0
  148. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/auth.setup.ts +49 -0
  149. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/auth.spec.ts +134 -0
  150. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/chat.spec.ts +207 -0
  151. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/home.spec.ts +73 -0
  152. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/instrumentation.ts +14 -0
  153. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/messages/en.json +84 -0
  154. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/messages/pl.json +84 -0
  155. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/next.config.ts +76 -0
  156. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/package.json +69 -0
  157. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/playwright.config.ts +101 -0
  158. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/postcss.config.mjs +7 -0
  159. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(auth)/layout.tsx +11 -0
  160. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(auth)/login/page.tsx +5 -0
  161. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(auth)/register/page.tsx +5 -0
  162. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/chat/page.tsx +48 -0
  163. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/dashboard/page.tsx +99 -0
  164. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/layout.tsx +17 -0
  165. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/profile/page.tsx +152 -0
  166. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/auth/callback/page.tsx +113 -0
  167. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/layout.tsx +46 -0
  168. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/page.tsx +73 -0
  169. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/login/route.ts +58 -0
  170. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/logout/route.ts +24 -0
  171. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/me/route.ts +39 -0
  172. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/oauth-callback/route.ts +50 -0
  173. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/refresh/route.ts +54 -0
  174. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/register/route.ts +26 -0
  175. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/[id]/messages/route.ts +41 -0
  176. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/[id]/route.ts +108 -0
  177. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/route.ts +73 -0
  178. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/health/route.ts +21 -0
  179. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/globals.css +323 -0
  180. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/layout.tsx +22 -0
  181. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/providers.tsx +29 -0
  182. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/index.ts +2 -0
  183. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/login-form.tsx +120 -0
  184. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/register-form.tsx +153 -0
  185. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/chat-container.tsx +234 -0
  186. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/chat-input.tsx +72 -0
  187. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/conversation-sidebar.tsx +328 -0
  188. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/copy-button.tsx +46 -0
  189. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/index.ts +11 -0
  190. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/local-conversation-sidebar.tsx +295 -0
  191. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/markdown-content.tsx +167 -0
  192. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/message-item.tsx +79 -0
  193. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/message-list.tsx +18 -0
  194. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/tool-call-card.tsx +79 -0
  195. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/icons/google-icon.tsx +32 -0
  196. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/icons/index.ts +3 -0
  197. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/language-switcher.tsx +97 -0
  198. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/header.tsx +65 -0
  199. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/index.ts +2 -0
  200. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/sidebar.tsx +82 -0
  201. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/index.ts +7 -0
  202. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/theme-provider.tsx +53 -0
  203. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/theme-toggle.tsx +105 -0
  204. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/badge.tsx +35 -0
  205. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/button.test.tsx +75 -0
  206. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/button.tsx +56 -0
  207. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/card.tsx +82 -0
  208. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/index.ts +13 -0
  209. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/input.tsx +21 -0
  210. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/label.tsx +21 -0
  211. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/sheet.tsx +109 -0
  212. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/index.ts +7 -0
  213. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-auth.ts +97 -0
  214. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-chat.ts +203 -0
  215. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-conversations.ts +181 -0
  216. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-local-chat.ts +165 -0
  217. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-websocket.ts +105 -0
  218. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/i18n.ts +37 -0
  219. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/api-client.ts +90 -0
  220. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/constants.ts +39 -0
  221. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/server-api.ts +78 -0
  222. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/utils.test.ts +44 -0
  223. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/utils.ts +44 -0
  224. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/middleware.ts +31 -0
  225. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/auth-store.test.ts +72 -0
  226. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/auth-store.ts +64 -0
  227. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/chat-sidebar-store.ts +17 -0
  228. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/chat-store.ts +65 -0
  229. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/conversation-store.ts +76 -0
  230. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/index.ts +9 -0
  231. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/local-chat-store.ts +255 -0
  232. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/sidebar-store.ts +17 -0
  233. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/theme-store.ts +44 -0
  234. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/api.ts +27 -0
  235. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/auth.ts +52 -0
  236. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/chat.ts +83 -0
  237. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/conversation.ts +49 -0
  238. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/index.ts +10 -0
  239. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/tsconfig.json +28 -0
  240. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/vitest.config.ts +36 -0
  241. fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/vitest.setup.ts +56 -0
@@ -0,0 +1,97 @@
1
+ {%- if cookiecutter.enable_i18n %}
2
+ 'use client';
3
+
4
+ import { useLocale } from 'next-intl';
5
+ import { useRouter, usePathname } from 'next/navigation';
6
+ import { locales, type Locale, getLocaleLabel } from '@/i18n';
7
+
8
+ /**
9
+ * Language switcher dropdown component.
10
+ * Allows users to switch between available locales.
11
+ */
12
+ export function LanguageSwitcher() {
13
+ const locale = useLocale();
14
+ const router = useRouter();
15
+ const pathname = usePathname();
16
+
17
+ const handleChange = (newLocale: Locale) => {
18
+ // Remove the current locale from pathname and add the new one
19
+ const segments = pathname.split('/');
20
+ segments[1] = newLocale;
21
+ const newPath = segments.join('/');
22
+ router.push(newPath);
23
+ };
24
+
25
+ return (
26
+ <div className="relative">
27
+ <select
28
+ value={locale}
29
+ onChange={(e) => handleChange(e.target.value as Locale)}
30
+ className="appearance-none bg-transparent border border-gray-300 dark:border-gray-600 rounded-md px-3 py-1.5 pr-8 text-sm cursor-pointer hover:border-gray-400 dark:hover:border-gray-500 focus:outline-none focus:ring-2 focus:ring-blue-500"
31
+ aria-label="Select language"
32
+ >
33
+ {locales.map((loc) => (
34
+ <option key={loc} value={loc}>
35
+ {getLocaleLabel(loc)}
36
+ </option>
37
+ ))}
38
+ </select>
39
+ <div className="pointer-events-none absolute inset-y-0 right-0 flex items-center px-2 text-gray-500">
40
+ <svg className="h-4 w-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
41
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M19 9l-7 7-7-7" />
42
+ </svg>
43
+ </div>
44
+ </div>
45
+ );
46
+ }
47
+
48
+ /**
49
+ * Compact language switcher with flag icons.
50
+ */
51
+ export function LanguageSwitcherCompact() {
52
+ const locale = useLocale();
53
+ const router = useRouter();
54
+ const pathname = usePathname();
55
+
56
+ const flags: Record<Locale, string> = {
57
+ en: '🇬🇧',
58
+ pl: '🇵🇱',
59
+ };
60
+
61
+ const handleChange = (newLocale: Locale) => {
62
+ const segments = pathname.split('/');
63
+ segments[1] = newLocale;
64
+ const newPath = segments.join('/');
65
+ router.push(newPath);
66
+ };
67
+
68
+ return (
69
+ <div className="flex gap-1">
70
+ {locales.map((loc) => (
71
+ <button
72
+ key={loc}
73
+ onClick={() => handleChange(loc)}
74
+ className={`px-2 py-1 rounded-md text-lg transition-opacity ${
75
+ locale === loc
76
+ ? 'opacity-100 bg-gray-100 dark:bg-gray-800'
77
+ : 'opacity-50 hover:opacity-75'
78
+ }`}
79
+ aria-label={getLocaleLabel(loc)}
80
+ aria-pressed={locale === loc}
81
+ >
82
+ {flags[loc]}
83
+ </button>
84
+ ))}
85
+ </div>
86
+ );
87
+ }
88
+ {%- else %}
89
+ // i18n is disabled - no language switcher component
90
+ export function LanguageSwitcher() {
91
+ return null;
92
+ }
93
+
94
+ export function LanguageSwitcherCompact() {
95
+ return null;
96
+ }
97
+ {%- endif %}
@@ -0,0 +1,65 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { useAuth } from "@/hooks";
5
+ import { Button } from "@/components/ui";
6
+ import { ThemeToggle } from "@/components/theme";
7
+ {%- if cookiecutter.enable_i18n %}
8
+ import { LanguageSwitcherCompact } from "@/components/language-switcher";
9
+ {%- endif %}
10
+ import { ROUTES } from "@/lib/constants";
11
+ import { LogOut, User, Menu } from "lucide-react";
12
+ import { useSidebarStore } from "@/stores";
13
+
14
+ export function Header() {
15
+ const { user, isAuthenticated, logout } = useAuth();
16
+ const { toggle } = useSidebarStore();
17
+
18
+ return (
19
+ <header className="sticky top-0 z-40 w-full border-b bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
20
+ <div className="flex h-14 items-center justify-between px-3 sm:px-6">
21
+ <Button
22
+ variant="ghost"
23
+ size="sm"
24
+ className="h-10 w-10 p-0 md:hidden"
25
+ onClick={toggle}
26
+ >
27
+ <Menu className="h-5 w-5" />
28
+ <span className="sr-only">Toggle menu</span>
29
+ </Button>
30
+
31
+ <div className="hidden md:block" />
32
+
33
+ <div className="flex items-center gap-2 sm:gap-3">
34
+ {%- if cookiecutter.enable_i18n %}
35
+ <LanguageSwitcherCompact />
36
+ {%- endif %}
37
+ <ThemeToggle />
38
+ {isAuthenticated ? (
39
+ <>
40
+ <Button variant="ghost" size="sm" asChild className="h-10 px-2 sm:px-3">
41
+ <Link href={ROUTES.PROFILE} className="flex items-center gap-2">
42
+ <User className="h-4 w-4" />
43
+ <span className="hidden max-w-32 truncate sm:inline">{user?.email}</span>
44
+ </Link>
45
+ </Button>
46
+ <Button variant="ghost" size="sm" onClick={logout} className="h-10 w-10 p-0 sm:w-auto sm:px-3">
47
+ <LogOut className="h-4 w-4" />
48
+ <span className="sr-only sm:not-sr-only sm:ml-2">Logout</span>
49
+ </Button>
50
+ </>
51
+ ) : (
52
+ <>
53
+ <Button variant="ghost" size="sm" asChild className="h-10">
54
+ <Link href={ROUTES.LOGIN}>Login</Link>
55
+ </Button>
56
+ <Button size="sm" asChild className="h-10">
57
+ <Link href={ROUTES.REGISTER}>Register</Link>
58
+ </Button>
59
+ </>
60
+ )}
61
+ </div>
62
+ </div>
63
+ </header>
64
+ );
65
+ }
@@ -0,0 +1,2 @@
1
+ export { Header } from "./header";
2
+ export { Sidebar } from "./sidebar";
@@ -0,0 +1,82 @@
1
+ "use client";
2
+
3
+ import Link from "next/link";
4
+ import { usePathname } from "next/navigation";
5
+ import { cn } from "@/lib/utils";
6
+ import { ROUTES } from "@/lib/constants";
7
+ import { LayoutDashboard, MessageSquare } from "lucide-react";
8
+ import { useSidebarStore } from "@/stores";
9
+ import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetClose } from "@/components/ui";
10
+
11
+ const navigation = [
12
+ { name: "Dashboard", href: ROUTES.DASHBOARD, icon: LayoutDashboard },
13
+ { name: "Chat", href: ROUTES.CHAT, icon: MessageSquare },
14
+ ];
15
+
16
+ function NavLinks({ onNavigate }: { onNavigate?: () => void }) {
17
+ const pathname = usePathname();
18
+
19
+ return (
20
+ <nav className="flex-1 space-y-1 p-4">
21
+ {navigation.map((item) => {
22
+ const isActive = pathname === item.href;
23
+ return (
24
+ <Link
25
+ key={item.name}
26
+ href={item.href}
27
+ onClick={onNavigate}
28
+ className={cn(
29
+ "flex items-center gap-3 rounded-lg px-3 py-3 text-sm font-medium transition-colors",
30
+ "min-h-[44px]",
31
+ isActive
32
+ ? "bg-secondary text-secondary-foreground"
33
+ : "text-muted-foreground hover:bg-secondary/50 hover:text-secondary-foreground"
34
+ )}
35
+ >
36
+ <item.icon className="h-5 w-5" />
37
+ {item.name}
38
+ </Link>
39
+ );
40
+ })}
41
+ </nav>
42
+ );
43
+ }
44
+
45
+ function SidebarContent({ onNavigate }: { onNavigate?: () => void }) {
46
+ return (
47
+ <div className="flex h-full flex-col">
48
+ <div className="flex h-14 items-center border-b px-4">
49
+ <Link
50
+ href={ROUTES.HOME}
51
+ className="flex items-center gap-2 font-semibold"
52
+ onClick={onNavigate}
53
+ >
54
+ <span>{"{{ cookiecutter.project_name }}"}</span>
55
+ </Link>
56
+ </div>
57
+ <NavLinks onNavigate={onNavigate} />
58
+ </div>
59
+ );
60
+ }
61
+
62
+ export function Sidebar() {
63
+ const { isOpen, close } = useSidebarStore();
64
+
65
+ return (
66
+ <>
67
+ <aside className="hidden w-64 shrink-0 border-r bg-background md:block">
68
+ <SidebarContent />
69
+ </aside>
70
+
71
+ <Sheet open={isOpen} onOpenChange={close}>
72
+ <SheetContent side="left" className="w-72 p-0">
73
+ <SheetHeader className="h-14 px-4">
74
+ <SheetTitle>{"{{ cookiecutter.project_name }}"}</SheetTitle>
75
+ <SheetClose onClick={close} />
76
+ </SheetHeader>
77
+ <NavLinks onNavigate={close} />
78
+ </SheetContent>
79
+ </Sheet>
80
+ </>
81
+ );
82
+ }
@@ -0,0 +1,7 @@
1
+ {%- if cookiecutter.use_frontend %}
2
+ export { ThemeProvider } from "./theme-provider";
3
+ export { ThemeToggle } from "./theme-toggle";
4
+ {%- else %}
5
+ /* Theme components - frontend not configured */
6
+ export {};
7
+ {%- endif %}
@@ -0,0 +1,53 @@
1
+ {%- if cookiecutter.use_frontend %}
2
+ "use client";
3
+
4
+ import { useEffect } from "react";
5
+ import { useThemeStore, getResolvedTheme } from "@/stores/theme-store";
6
+
7
+ interface ThemeProviderProps {
8
+ children: React.ReactNode;
9
+ }
10
+
11
+ export function ThemeProvider({ children }: ThemeProviderProps) {
12
+ const { theme } = useThemeStore();
13
+
14
+ useEffect(() => {
15
+ const root = document.documentElement;
16
+ const resolvedTheme = getResolvedTheme(theme);
17
+
18
+ // Remove both classes first
19
+ root.classList.remove("light", "dark");
20
+ // Add the current theme class
21
+ root.classList.add(resolvedTheme);
22
+
23
+ // Update color-scheme for native elements
24
+ root.style.colorScheme = resolvedTheme;
25
+ }, [theme]);
26
+
27
+ // Listen for system theme changes when using "system" theme
28
+ useEffect(() => {
29
+ if (theme !== "system") return;
30
+
31
+ const mediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
32
+
33
+ const handleChange = () => {
34
+ const root = document.documentElement;
35
+ const resolvedTheme = mediaQuery.matches ? "dark" : "light";
36
+
37
+ root.classList.remove("light", "dark");
38
+ root.classList.add(resolvedTheme);
39
+ root.style.colorScheme = resolvedTheme;
40
+ };
41
+
42
+ mediaQuery.addEventListener("change", handleChange);
43
+ return () => mediaQuery.removeEventListener("change", handleChange);
44
+ }, [theme]);
45
+
46
+ return <>{children}</>;
47
+ }
48
+ {%- else %}
49
+ /* Theme provider - frontend not configured */
50
+ export function ThemeProvider({ children }: { children: React.ReactNode }) {
51
+ return <>{children}</>;
52
+ }
53
+ {%- endif %}
@@ -0,0 +1,105 @@
1
+ {%- if cookiecutter.use_frontend %}
2
+ "use client";
3
+
4
+ import { useEffect, useState } from "react";
5
+ import { Moon, Sun, Monitor } from "lucide-react";
6
+ import { Button } from "@/components/ui/button";
7
+ import { useThemeStore, Theme, getResolvedTheme } from "@/stores/theme-store";
8
+
9
+ interface ThemeToggleProps {
10
+ variant?: "icon" | "dropdown";
11
+ className?: string;
12
+ }
13
+
14
+ export function ThemeToggle({ variant = "icon", className }: ThemeToggleProps) {
15
+ const { theme, setTheme } = useThemeStore();
16
+ const [mounted, setMounted] = useState(false);
17
+
18
+ // Prevent hydration mismatch by only rendering after mount
19
+ useEffect(() => {
20
+ setMounted(true);
21
+ }, []);
22
+
23
+ const resolvedTheme = getResolvedTheme(theme);
24
+
25
+ const cycleTheme = () => {
26
+ const themes: Theme[] = ["light", "dark", "system"];
27
+ const currentIndex = themes.indexOf(theme);
28
+ const nextIndex = (currentIndex + 1) % themes.length;
29
+ setTheme(themes[nextIndex]);
30
+ };
31
+
32
+ // Render placeholder during SSR to prevent hydration mismatch
33
+ if (!mounted) {
34
+ return (
35
+ <Button
36
+ variant="ghost"
37
+ size="icon"
38
+ className={className}
39
+ aria-label="Toggle theme"
40
+ >
41
+ <Sun className="h-5 w-5" />
42
+ </Button>
43
+ );
44
+ }
45
+
46
+ if (variant === "icon") {
47
+ return (
48
+ <Button
49
+ variant="ghost"
50
+ size="icon"
51
+ onClick={cycleTheme}
52
+ className={className}
53
+ aria-label={`Switch theme (current: ${theme})`}
54
+ title={`Theme: ${theme}`}
55
+ >
56
+ {resolvedTheme === "dark" ? (
57
+ <Moon className="h-5 w-5" />
58
+ ) : (
59
+ <Sun className="h-5 w-5" />
60
+ )}
61
+ {theme === "system" && (
62
+ <span className="sr-only">(following system)</span>
63
+ )}
64
+ </Button>
65
+ );
66
+ }
67
+
68
+ return (
69
+ <div className={`flex gap-1 ${className}`}>
70
+ <Button
71
+ variant={theme === "light" ? "default" : "ghost"}
72
+ size="icon"
73
+ onClick={() => setTheme("light")}
74
+ aria-label="Light mode"
75
+ title="Light mode"
76
+ >
77
+ <Sun className="h-4 w-4" />
78
+ </Button>
79
+ <Button
80
+ variant={theme === "dark" ? "default" : "ghost"}
81
+ size="icon"
82
+ onClick={() => setTheme("dark")}
83
+ aria-label="Dark mode"
84
+ title="Dark mode"
85
+ >
86
+ <Moon className="h-4 w-4" />
87
+ </Button>
88
+ <Button
89
+ variant={theme === "system" ? "default" : "ghost"}
90
+ size="icon"
91
+ onClick={() => setTheme("system")}
92
+ aria-label="System theme"
93
+ title="System theme"
94
+ >
95
+ <Monitor className="h-4 w-4" />
96
+ </Button>
97
+ </div>
98
+ );
99
+ }
100
+ {%- else %}
101
+ /* Theme toggle - frontend not configured */
102
+ export function ThemeToggle() {
103
+ return null;
104
+ }
105
+ {%- endif %}
@@ -0,0 +1,35 @@
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+ import { cn } from "@/lib/utils";
4
+
5
+ const badgeVariants = cva(
6
+ "inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default:
11
+ "border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80",
12
+ secondary:
13
+ "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80",
14
+ destructive:
15
+ "border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80",
16
+ outline: "text-foreground",
17
+ },
18
+ },
19
+ defaultVariants: {
20
+ variant: "default",
21
+ },
22
+ }
23
+ );
24
+
25
+ export interface BadgeProps
26
+ extends React.HTMLAttributes<HTMLDivElement>,
27
+ VariantProps<typeof badgeVariants> {}
28
+
29
+ function Badge({ className, variant, ...props }: BadgeProps) {
30
+ return (
31
+ <div className={cn(badgeVariants({ variant }), className)} {...props} />
32
+ );
33
+ }
34
+
35
+ export { Badge, badgeVariants };
@@ -0,0 +1,75 @@
1
+ {%- if cookiecutter.use_frontend %}
2
+ import { describe, it, expect, vi } from "vitest";
3
+ import { render, screen, fireEvent } from "@testing-library/react";
4
+ import { Button } from "./button";
5
+
6
+ describe("Button component", () => {
7
+ it("should render with children", () => {
8
+ render(<Button>Click me</Button>);
9
+ expect(screen.getByRole("button", { name: /click me/i })).toBeInTheDocument();
10
+ });
11
+
12
+ it("should handle click events", () => {
13
+ const handleClick = vi.fn();
14
+ render(<Button onClick={handleClick}>Click me</Button>);
15
+
16
+ fireEvent.click(screen.getByRole("button"));
17
+ expect(handleClick).toHaveBeenCalledTimes(1);
18
+ });
19
+
20
+ it("should apply variant classes", () => {
21
+ render(<Button variant="destructive">Delete</Button>);
22
+ const button = screen.getByRole("button");
23
+ expect(button.className).toMatch(/destructive|red|danger/i);
24
+ });
25
+
26
+ it("should apply size classes", () => {
27
+ render(<Button size="lg">Large Button</Button>);
28
+ const button = screen.getByRole("button");
29
+ // Check that the button has appropriate size styling
30
+ expect(button).toBeInTheDocument();
31
+ });
32
+
33
+ it("should be disabled when disabled prop is true", () => {
34
+ render(<Button disabled>Disabled</Button>);
35
+ const button = screen.getByRole("button");
36
+ expect(button).toBeDisabled();
37
+ });
38
+
39
+ it("should not trigger click when disabled", () => {
40
+ const handleClick = vi.fn();
41
+ render(
42
+ <Button disabled onClick={handleClick}>
43
+ Disabled
44
+ </Button>
45
+ );
46
+
47
+ fireEvent.click(screen.getByRole("button"));
48
+ expect(handleClick).not.toHaveBeenCalled();
49
+ });
50
+
51
+ it("should support custom className", () => {
52
+ render(<Button className="custom-class">Custom</Button>);
53
+ const button = screen.getByRole("button");
54
+ expect(button.className).toContain("custom-class");
55
+ });
56
+
57
+ it("should render as a link when asChild is used", () => {
58
+ render(
59
+ <Button asChild>
60
+ <a href="/test">Link Button</a>
61
+ </Button>
62
+ );
63
+ expect(screen.getByRole("link")).toBeInTheDocument();
64
+ });
65
+
66
+ it("should support different button types", () => {
67
+ render(<Button type="submit">Submit</Button>);
68
+ const button = screen.getByRole("button");
69
+ expect(button).toHaveAttribute("type", "submit");
70
+ });
71
+ });
72
+ {%- else %}
73
+ /* Button tests - frontend not configured */
74
+ export {};
75
+ {%- endif %}
@@ -0,0 +1,56 @@
1
+ import * as React from "react";
2
+ import { cva, type VariantProps } from "class-variance-authority";
3
+ import { cn } from "@/lib/utils";
4
+
5
+ const buttonVariants = cva(
6
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0",
7
+ {
8
+ variants: {
9
+ variant: {
10
+ default:
11
+ "bg-primary text-primary-foreground shadow hover:bg-primary/90",
12
+ destructive:
13
+ "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
14
+ outline:
15
+ "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
16
+ secondary:
17
+ "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
18
+ ghost: "hover:bg-accent hover:text-accent-foreground",
19
+ link: "text-primary underline-offset-4 hover:underline",
20
+ },
21
+ size: {
22
+ default: "h-9 px-4 py-2",
23
+ sm: "h-8 rounded-md px-3 text-xs",
24
+ lg: "h-10 rounded-md px-8",
25
+ icon: "h-9 w-9",
26
+ },
27
+ },
28
+ defaultVariants: {
29
+ variant: "default",
30
+ size: "default",
31
+ },
32
+ }
33
+ );
34
+
35
+ export interface ButtonProps
36
+ extends React.ButtonHTMLAttributes<HTMLButtonElement>,
37
+ VariantProps<typeof buttonVariants> {
38
+ asChild?: boolean;
39
+ }
40
+
41
+ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
42
+ ({ className, variant, size, asChild: _asChild, ...props }, ref) => {
43
+ // Note: asChild is extracted but not used - this component doesn't support Slot rendering
44
+ // If you need asChild support, install @radix-ui/react-slot and use Slot component
45
+ return (
46
+ <button
47
+ className={cn(buttonVariants({ variant, size, className }))}
48
+ ref={ref}
49
+ {...props}
50
+ />
51
+ );
52
+ }
53
+ );
54
+ Button.displayName = "Button";
55
+
56
+ export { Button, buttonVariants };
@@ -0,0 +1,82 @@
1
+ import * as React from "react";
2
+ import { cn } from "@/lib/utils";
3
+
4
+ const Card = React.forwardRef<
5
+ HTMLDivElement,
6
+ React.HTMLAttributes<HTMLDivElement>
7
+ >(({ className, ...props }, ref) => (
8
+ <div
9
+ ref={ref}
10
+ className={cn(
11
+ "rounded-xl border bg-card text-card-foreground shadow",
12
+ className
13
+ )}
14
+ {...props}
15
+ />
16
+ ));
17
+ Card.displayName = "Card";
18
+
19
+ const CardHeader = React.forwardRef<
20
+ HTMLDivElement,
21
+ React.HTMLAttributes<HTMLDivElement>
22
+ >(({ className, ...props }, ref) => (
23
+ <div
24
+ ref={ref}
25
+ className={cn("flex flex-col space-y-1.5 p-6", className)}
26
+ {...props}
27
+ />
28
+ ));
29
+ CardHeader.displayName = "CardHeader";
30
+
31
+ const CardTitle = React.forwardRef<
32
+ HTMLDivElement,
33
+ React.HTMLAttributes<HTMLDivElement>
34
+ >(({ className, ...props }, ref) => (
35
+ <div
36
+ ref={ref}
37
+ className={cn("font-semibold leading-none tracking-tight", className)}
38
+ {...props}
39
+ />
40
+ ));
41
+ CardTitle.displayName = "CardTitle";
42
+
43
+ const CardDescription = React.forwardRef<
44
+ HTMLDivElement,
45
+ React.HTMLAttributes<HTMLDivElement>
46
+ >(({ className, ...props }, ref) => (
47
+ <div
48
+ ref={ref}
49
+ className={cn("text-sm text-muted-foreground", className)}
50
+ {...props}
51
+ />
52
+ ));
53
+ CardDescription.displayName = "CardDescription";
54
+
55
+ const CardContent = React.forwardRef<
56
+ HTMLDivElement,
57
+ React.HTMLAttributes<HTMLDivElement>
58
+ >(({ className, ...props }, ref) => (
59
+ <div ref={ref} className={cn("p-6 pt-0", className)} {...props} />
60
+ ));
61
+ CardContent.displayName = "CardContent";
62
+
63
+ const CardFooter = React.forwardRef<
64
+ HTMLDivElement,
65
+ React.HTMLAttributes<HTMLDivElement>
66
+ >(({ className, ...props }, ref) => (
67
+ <div
68
+ ref={ref}
69
+ className={cn("flex items-center p-6 pt-0", className)}
70
+ {...props}
71
+ />
72
+ ));
73
+ CardFooter.displayName = "CardFooter";
74
+
75
+ export {
76
+ Card,
77
+ CardHeader,
78
+ CardFooter,
79
+ CardTitle,
80
+ CardDescription,
81
+ CardContent,
82
+ };
@@ -0,0 +1,13 @@
1
+ export { Button, buttonVariants } from "./button";
2
+ export { Input } from "./input";
3
+ export { Label } from "./label";
4
+ export {
5
+ Card,
6
+ CardHeader,
7
+ CardFooter,
8
+ CardTitle,
9
+ CardDescription,
10
+ CardContent,
11
+ } from "./card";
12
+ export { Badge, badgeVariants } from "./badge";
13
+ export { Sheet, SheetContent, SheetHeader, SheetTitle, SheetClose } from "./sheet";