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,69 @@
1
+ {
2
+ "name": "{{ cookiecutter.project_slug }}-frontend",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "scripts": {
6
+ "dev": "next dev -p {{ cookiecutter.frontend_port }}",
7
+ "build": "next build",
8
+ "start": "next start -p {{ cookiecutter.frontend_port }}",
9
+ "lint": "next lint",
10
+ "lint:fix": "next lint --fix",
11
+ "format": "prettier --write .",
12
+ "format:check": "prettier --check .",
13
+ "type-check": "tsc --noEmit",
14
+ "test:e2e": "playwright test",
15
+ "test:e2e:ui": "playwright test --ui",
16
+ "test:e2e:headed": "playwright test --headed",
17
+ "test:e2e:debug": "playwright test --debug",
18
+ "test:e2e:report": "playwright show-report",
19
+ "test": "vitest",
20
+ "test:run": "vitest run",
21
+ "test:coverage": "vitest run --coverage",
22
+ "test:ui": "vitest --ui"
23
+ },
24
+ "dependencies": {
25
+ "next": "^15.1.0",
26
+ "react": "^19.0.0",
27
+ "react-dom": "^19.0.0",
28
+ "@tanstack/react-query": "^5.62.0",
29
+ "zustand": "^5.0.2",
30
+ "nanoid": "^5.0.9",
31
+ "lucide-react": "^0.468.0",
32
+ "clsx": "^2.1.1",
33
+ "tailwind-merge": "^2.6.0",
34
+ {%- if cookiecutter.enable_logfire %}
35
+ "@vercel/otel": "^1.10.0",
36
+ "@opentelemetry/api": "^1.9.0",
37
+ {%- endif %}
38
+ {%- if cookiecutter.enable_i18n %}
39
+ "next-intl": "^3.25.3",
40
+ {%- endif %}
41
+ "class-variance-authority": "^0.7.1",
42
+ "react-markdown": "^9.0.1",
43
+ "remark-gfm": "^4.0.0",
44
+ "rehype-highlight": "^7.0.0"
45
+ },
46
+ "devDependencies": {
47
+ "typescript": "^5.7.2",
48
+ "@types/node": "^22.10.2",
49
+ "@types/react": "^19.0.1",
50
+ "@types/react-dom": "^19.0.2",
51
+ "tailwindcss": "^4.0.0-beta.8",
52
+ "@tailwindcss/postcss": "^4.0.0-beta.8",
53
+ "postcss": "^8.4.49",
54
+ "eslint": "^9.17.0",
55
+ "eslint-config-next": "^15.1.0",
56
+ "eslint-config-prettier": "^9.1.0",
57
+ "prettier": "^3.4.2",
58
+ "prettier-plugin-tailwindcss": "^0.6.9",
59
+ "@playwright/test": "^1.49.1",
60
+ "vitest": "^2.1.8",
61
+ "@vitejs/plugin-react": "^4.3.4",
62
+ "@testing-library/react": "^16.1.0",
63
+ "@testing-library/jest-dom": "^6.6.3",
64
+ "@testing-library/user-event": "^14.5.2",
65
+ "jsdom": "^25.0.1",
66
+ "@vitest/coverage-v8": "^2.1.8",
67
+ "@vitest/ui": "^2.1.8"
68
+ }
69
+ }
@@ -0,0 +1,101 @@
1
+ {%- if cookiecutter.use_frontend %}
2
+ import { defineConfig, devices } from "@playwright/test";
3
+
4
+ /**
5
+ * Playwright E2E test configuration.
6
+ *
7
+ * See https://playwright.dev/docs/test-configuration.
8
+ */
9
+ export default defineConfig({
10
+ testDir: "./e2e",
11
+ /* Run tests in files in parallel */
12
+ fullyParallel: true,
13
+ /* Fail the build on CI if you accidentally left test.only in the source code. */
14
+ forbidOnly: !!process.env.CI,
15
+ /* Retry on CI only */
16
+ retries: process.env.CI ? 2 : 0,
17
+ /* Opt out of parallel tests on CI. */
18
+ workers: process.env.CI ? 1 : undefined,
19
+ /* Reporter to use. See https://playwright.dev/docs/test-reporters */
20
+ reporter: [
21
+ ["list"],
22
+ ["html", { outputFolder: "playwright-report" }],
23
+ ],
24
+ /* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
25
+ use: {
26
+ /* Base URL to use in actions like `await page.goto('/')`. */
27
+ baseURL: process.env.PLAYWRIGHT_BASE_URL || "http://localhost:{{ cookiecutter.frontend_port }}",
28
+
29
+ /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
30
+ trace: "on-first-retry",
31
+
32
+ /* Capture screenshot on failure */
33
+ screenshot: "only-on-failure",
34
+
35
+ /* Video recording on failure */
36
+ video: "on-first-retry",
37
+ },
38
+
39
+ /* Configure projects for major browsers */
40
+ projects: [
41
+ /* Authentication setup - runs first */
42
+ {
43
+ name: "setup",
44
+ testMatch: /.*\.setup\.ts/,
45
+ },
46
+
47
+ {
48
+ name: "chromium",
49
+ use: {
50
+ ...devices["Desktop Chrome"],
51
+ },
52
+ dependencies: ["setup"],
53
+ },
54
+
55
+ {
56
+ name: "firefox",
57
+ use: {
58
+ ...devices["Desktop Firefox"],
59
+ },
60
+ dependencies: ["setup"],
61
+ },
62
+
63
+ {
64
+ name: "webkit",
65
+ use: {
66
+ ...devices["Desktop Safari"],
67
+ },
68
+ dependencies: ["setup"],
69
+ },
70
+
71
+ /* Test against mobile viewports. */
72
+ {
73
+ name: "Mobile Chrome",
74
+ use: {
75
+ ...devices["Pixel 5"],
76
+ },
77
+ dependencies: ["setup"],
78
+ },
79
+ {
80
+ name: "Mobile Safari",
81
+ use: {
82
+ ...devices["iPhone 12"],
83
+ },
84
+ dependencies: ["setup"],
85
+ },
86
+ ],
87
+
88
+ /* Run your local dev server before starting the tests */
89
+ webServer: process.env.CI
90
+ ? undefined
91
+ : {
92
+ command: "bun run dev",
93
+ url: "http://localhost:{{ cookiecutter.frontend_port }}",
94
+ reuseExistingServer: !process.env.CI,
95
+ timeout: 120 * 1000,
96
+ },
97
+ });
98
+ {%- else %}
99
+ /* Playwright config - frontend not configured */
100
+ export {};
101
+ {%- endif %}
@@ -0,0 +1,7 @@
1
+ const config = {
2
+ plugins: {
3
+ "@tailwindcss/postcss": {},
4
+ },
5
+ };
6
+
7
+ export default config;
@@ -0,0 +1,11 @@
1
+ export default function AuthLayout({
2
+ children,
3
+ }: {
4
+ children: React.ReactNode;
5
+ }) {
6
+ return (
7
+ <div className="min-h-screen flex items-center justify-center bg-background px-4">
8
+ {children}
9
+ </div>
10
+ );
11
+ }
@@ -0,0 +1,5 @@
1
+ import { LoginForm } from "@/components/auth";
2
+
3
+ export default function LoginPage() {
4
+ return <LoginForm />;
5
+ }
@@ -0,0 +1,5 @@
1
+ import { RegisterForm } from "@/components/auth";
2
+
3
+ export default function RegisterPage() {
4
+ return <RegisterForm />;
5
+ }
@@ -0,0 +1,48 @@
1
+ {%- if cookiecutter.enable_conversation_persistence and cookiecutter.use_database %}
2
+ "use client";
3
+
4
+ import { ChatContainer, ConversationSidebar, LocalConversationSidebar, ChatSidebarToggle } from "@/components/chat";
5
+ import { useAuthStore } from "@/stores";
6
+
7
+ export default function ChatPage() {
8
+ const { isAuthenticated } = useAuthStore();
9
+
10
+ const Sidebar = isAuthenticated ? ConversationSidebar : LocalConversationSidebar;
11
+
12
+ return (
13
+ <div className="flex h-full -m-3 sm:-m-6">
14
+ <Sidebar />
15
+ <div className="flex-1 min-w-0 flex flex-col">
16
+ <div className="flex items-center gap-2 p-2 border-b md:hidden">
17
+ <ChatSidebarToggle />
18
+ <span className="text-sm font-medium">Chat</span>
19
+ </div>
20
+ <div className="flex-1 min-h-0">
21
+ <ChatContainer />
22
+ </div>
23
+ </div>
24
+ </div>
25
+ );
26
+ }
27
+ {%- else %}
28
+ "use client";
29
+
30
+ import { ChatContainer, LocalConversationSidebar, ChatSidebarToggle } from "@/components/chat";
31
+
32
+ export default function ChatPage() {
33
+ return (
34
+ <div className="flex h-full -m-3 sm:-m-6">
35
+ <LocalConversationSidebar />
36
+ <div className="flex-1 min-w-0 flex flex-col">
37
+ <div className="flex items-center gap-2 p-2 border-b md:hidden">
38
+ <ChatSidebarToggle />
39
+ <span className="text-sm font-medium">Chat</span>
40
+ </div>
41
+ <div className="flex-1 min-h-0">
42
+ <ChatContainer />
43
+ </div>
44
+ </div>
45
+ </div>
46
+ );
47
+ }
48
+ {%- endif %}
@@ -0,0 +1,99 @@
1
+ "use client";
2
+
3
+ import { useEffect, useState } from "react";
4
+ import { Card, CardHeader, CardTitle, CardContent } from "@/components/ui";
5
+ import { apiClient } from "@/lib/api-client";
6
+ import { useAuth } from "@/hooks";
7
+ import type { HealthResponse } from "@/types";
8
+ import { CheckCircle, XCircle, Loader2 } from "lucide-react";
9
+
10
+ export default function DashboardPage() {
11
+ const { user } = useAuth();
12
+ const [health, setHealth] = useState<HealthResponse | null>(null);
13
+ const [healthLoading, setHealthLoading] = useState(true);
14
+ const [healthError, setHealthError] = useState(false);
15
+
16
+ useEffect(() => {
17
+ const checkHealth = async () => {
18
+ try {
19
+ const data = await apiClient.get<HealthResponse>("/health");
20
+ setHealth(data);
21
+ setHealthError(false);
22
+ } catch {
23
+ setHealthError(true);
24
+ } finally {
25
+ setHealthLoading(false);
26
+ }
27
+ };
28
+
29
+ checkHealth();
30
+ }, []);
31
+
32
+ return (
33
+ <div className="space-y-4 sm:space-y-6">
34
+ <div>
35
+ <h1 className="text-2xl sm:text-3xl font-bold">Dashboard</h1>
36
+ <p className="text-sm sm:text-base text-muted-foreground">
37
+ Welcome back{user?.name ? `, ${user.name}` : ""}!
38
+ </p>
39
+ </div>
40
+
41
+ <div className="grid gap-4 sm:gap-6 md:grid-cols-2 lg:grid-cols-3">
42
+ <Card>
43
+ <CardHeader className="pb-2 sm:pb-4">
44
+ <CardTitle className="flex items-center gap-2 text-base sm:text-lg">
45
+ API Status
46
+ {healthLoading ? (
47
+ <Loader2 className="h-4 w-4 animate-spin" />
48
+ ) : healthError ? (
49
+ <XCircle className="h-4 w-4 text-destructive" />
50
+ ) : (
51
+ <CheckCircle className="h-4 w-4 text-green-500" />
52
+ )}
53
+ </CardTitle>
54
+ </CardHeader>
55
+ <CardContent>
56
+ {healthLoading ? (
57
+ <p className="text-muted-foreground text-sm">Checking...</p>
58
+ ) : healthError ? (
59
+ <p className="text-destructive text-sm">Backend unavailable</p>
60
+ ) : (
61
+ <div className="space-y-1">
62
+ <p className="text-sm">
63
+ Status: <span className="font-medium">{health?.status}</span>
64
+ </p>
65
+ {health?.version && (
66
+ <p className="text-xs sm:text-sm text-muted-foreground">
67
+ Version: {health.version}
68
+ </p>
69
+ )}
70
+ </div>
71
+ )}
72
+ </CardContent>
73
+ </Card>
74
+
75
+ <Card>
76
+ <CardHeader className="pb-2 sm:pb-4">
77
+ <CardTitle className="text-base sm:text-lg">Your Account</CardTitle>
78
+ </CardHeader>
79
+ <CardContent>
80
+ {user ? (
81
+ <div className="space-y-1">
82
+ <p className="text-sm break-all">
83
+ Email: <span className="font-medium">{user.email}</span>
84
+ </p>
85
+ {user.name && (
86
+ <p className="text-sm">
87
+ Name: <span className="font-medium">{user.name}</span>
88
+ </p>
89
+ )}
90
+ </div>
91
+ ) : (
92
+ <p className="text-muted-foreground text-sm">Loading...</p>
93
+ )}
94
+ </CardContent>
95
+ </Card>
96
+ </div>
97
+ </div>
98
+ );
99
+ }
@@ -0,0 +1,17 @@
1
+ import { Header, Sidebar } from "@/components/layout";
2
+
3
+ export default function DashboardLayout({
4
+ children,
5
+ }: {
6
+ children: React.ReactNode;
7
+ }) {
8
+ return (
9
+ <div className="flex h-screen overflow-hidden">
10
+ <Sidebar />
11
+ <div className="flex min-w-0 flex-1 flex-col">
12
+ <Header />
13
+ <main className="flex-1 overflow-auto p-3 sm:p-6">{children}</main>
14
+ </div>
15
+ </div>
16
+ );
17
+ }
@@ -0,0 +1,152 @@
1
+ {%- if cookiecutter.use_frontend and cookiecutter.use_jwt %}
2
+ "use client";
3
+
4
+ import { useState } from "react";
5
+ import { useAuth } from "@/hooks";
6
+ import { Button, Card, Input, Label, Badge } from "@/components/ui";
7
+ import { ThemeToggle } from "@/components/theme";
8
+ import { User, Mail, Calendar, Shield, Settings } from "lucide-react";
9
+
10
+ export default function ProfilePage() {
11
+ const { user, isAuthenticated, logout } = useAuth();
12
+ const [isEditing, setIsEditing] = useState(false);
13
+
14
+ if (!isAuthenticated || !user) {
15
+ return (
16
+ <div className="flex min-h-[50vh] items-center justify-center">
17
+ <Card className="p-6 sm:p-8 text-center mx-4">
18
+ <p className="text-muted-foreground">Please log in to view your profile.</p>
19
+ </Card>
20
+ </div>
21
+ );
22
+ }
23
+
24
+ return (
25
+ <div className="container mx-auto max-w-4xl">
26
+ <div className="mb-6 sm:mb-8">
27
+ <h1 className="text-2xl sm:text-3xl font-bold tracking-tight">Profile</h1>
28
+ <p className="text-sm sm:text-base text-muted-foreground">
29
+ Manage your account settings and preferences
30
+ </p>
31
+ </div>
32
+
33
+ <div className="grid gap-4 sm:gap-6">
34
+ <Card className="p-4 sm:p-6">
35
+ <div className="flex flex-col sm:flex-row sm:items-start sm:justify-between gap-4">
36
+ <div className="flex items-center gap-3 sm:gap-4">
37
+ <div className="flex h-12 w-12 sm:h-16 sm:w-16 items-center justify-center rounded-full bg-primary/10 shrink-0">
38
+ <User className="h-6 w-6 sm:h-8 sm:w-8 text-primary" />
39
+ </div>
40
+ <div className="min-w-0">
41
+ <h2 className="text-lg sm:text-xl font-semibold truncate">{user.email}</h2>
42
+ <div className="mt-1 flex flex-wrap items-center gap-2">
43
+ {user.is_superuser && (
44
+ <Badge variant="secondary">
45
+ <Shield className="mr-1 h-3 w-3" />
46
+ Admin
47
+ </Badge>
48
+ )}
49
+ {user.is_active && (
50
+ <Badge variant="outline" className="text-green-600">
51
+ Active
52
+ </Badge>
53
+ )}
54
+ </div>
55
+ </div>
56
+ </div>
57
+ <Button
58
+ variant="outline"
59
+ size="sm"
60
+ onClick={() => setIsEditing(!isEditing)}
61
+ className="self-start h-10"
62
+ >
63
+ <Settings className="mr-2 h-4 w-4" />
64
+ {isEditing ? "Cancel" : "Edit"}
65
+ </Button>
66
+ </div>
67
+ </Card>
68
+
69
+ <Card className="p-4 sm:p-6">
70
+ <h3 className="mb-4 text-base sm:text-lg font-semibold">Account Information</h3>
71
+ <div className="grid gap-4">
72
+ <div className="grid gap-2">
73
+ <Label htmlFor="email" className="flex items-center gap-2 text-sm">
74
+ <Mail className="h-4 w-4 text-muted-foreground" />
75
+ Email Address
76
+ </Label>
77
+ <Input
78
+ id="email"
79
+ type="email"
80
+ value={user.email}
81
+ disabled={!isEditing}
82
+ className={!isEditing ? "bg-muted" : ""}
83
+ />
84
+ </div>
85
+
86
+ {user.created_at && (
87
+ <div className="flex items-center gap-2 text-xs sm:text-sm text-muted-foreground">
88
+ <Calendar className="h-4 w-4 shrink-0" />
89
+ <span>Member since {new Date(user.created_at).toLocaleDateString()}</span>
90
+ </div>
91
+ )}
92
+ </div>
93
+
94
+ {isEditing && (
95
+ <div className="mt-4 flex flex-col sm:flex-row justify-end gap-2">
96
+ <Button variant="outline" onClick={() => setIsEditing(false)} className="h-10">
97
+ Cancel
98
+ </Button>
99
+ <Button className="h-10">Save Changes</Button>
100
+ </div>
101
+ )}
102
+ </Card>
103
+
104
+ <Card className="p-4 sm:p-6">
105
+ <h3 className="mb-4 text-base sm:text-lg font-semibold">Preferences</h3>
106
+ <div className="flex items-center justify-between gap-4">
107
+ <div className="min-w-0">
108
+ <p className="font-medium text-sm sm:text-base">Theme</p>
109
+ <p className="text-xs sm:text-sm text-muted-foreground">
110
+ Choose your preferred color scheme
111
+ </p>
112
+ </div>
113
+ <ThemeToggle variant="dropdown" />
114
+ </div>
115
+ </Card>
116
+
117
+ <Card className="border-destructive/50 p-4 sm:p-6">
118
+ <h3 className="mb-4 text-base sm:text-lg font-semibold text-destructive">
119
+ Danger Zone
120
+ </h3>
121
+ <div className="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-4">
122
+ <div>
123
+ <p className="font-medium text-sm sm:text-base">Sign out</p>
124
+ <p className="text-xs sm:text-sm text-muted-foreground">
125
+ Sign out from your account on this device
126
+ </p>
127
+ </div>
128
+ <Button variant="destructive" onClick={logout} className="h-10 self-start sm:self-auto">
129
+ Sign Out
130
+ </Button>
131
+ </div>
132
+ </Card>
133
+ </div>
134
+ </div>
135
+ );
136
+ }
137
+ {%- elif cookiecutter.use_frontend %}
138
+ export default function ProfilePage() {
139
+ return (
140
+ <div className="container mx-auto">
141
+ <h1 className="text-2xl sm:text-3xl font-bold">Profile</h1>
142
+ <p className="mt-4 text-sm sm:text-base text-muted-foreground">
143
+ User authentication is not enabled.
144
+ </p>
145
+ </div>
146
+ );
147
+ }
148
+ {%- else %}
149
+ export default function ProfilePage() {
150
+ return null;
151
+ }
152
+ {%- endif %}
@@ -0,0 +1,113 @@
1
+ {%- if cookiecutter.enable_oauth %}
2
+ "use client";
3
+
4
+ import { Suspense, useEffect, useState } from "react";
5
+ import { useRouter, useSearchParams } from "next/navigation";
6
+ import { useAuthStore } from "@/stores/auth-store";
7
+ import { Card, CardContent } from "@/components/ui";
8
+ import { ROUTES } from "@/lib/constants";
9
+
10
+ function AuthCallbackContent() {
11
+ const router = useRouter();
12
+ const searchParams = useSearchParams();
13
+ const [error, setError] = useState<string | null>(null);
14
+ const { checkAuth } = useAuthStore();
15
+
16
+ useEffect(() => {
17
+ const handleCallback = async () => {
18
+ const accessToken = searchParams.get("access_token");
19
+ const refreshToken = searchParams.get("refresh_token");
20
+ const errorParam = searchParams.get("error");
21
+
22
+ if (errorParam) {
23
+ setError(errorParam);
24
+ setTimeout(() => {
25
+ router.push(ROUTES.LOGIN);
26
+ }, 3000);
27
+ return;
28
+ }
29
+
30
+ if (accessToken && refreshToken) {
31
+ // Store tokens - the API route will handle this
32
+ try {
33
+ const response = await fetch("/api/auth/oauth-callback", {
34
+ method: "POST",
35
+ headers: {
36
+ "Content-Type": "application/json",
37
+ },
38
+ body: JSON.stringify({ accessToken, refreshToken }),
39
+ });
40
+
41
+ if (response.ok) {
42
+ // Refresh auth state
43
+ await checkAuth();
44
+ router.push(ROUTES.DASHBOARD);
45
+ } else {
46
+ setError("Failed to complete authentication");
47
+ setTimeout(() => {
48
+ router.push(ROUTES.LOGIN);
49
+ }, 3000);
50
+ }
51
+ } catch {
52
+ setError("Authentication error");
53
+ setTimeout(() => {
54
+ router.push(ROUTES.LOGIN);
55
+ }, 3000);
56
+ }
57
+ } else {
58
+ setError("Missing authentication tokens");
59
+ setTimeout(() => {
60
+ router.push(ROUTES.LOGIN);
61
+ }, 3000);
62
+ }
63
+ };
64
+
65
+ handleCallback();
66
+ }, [searchParams, router, checkAuth]);
67
+
68
+ return (
69
+ <Card className="w-full max-w-md">
70
+ <CardContent className="pt-6">
71
+ {error ? (
72
+ <div className="text-center">
73
+ <p className="text-destructive mb-2">{error}</p>
74
+ <p className="text-muted-foreground text-sm">Redirecting to login...</p>
75
+ </div>
76
+ ) : (
77
+ <div className="text-center">
78
+ <div className="border-primary mx-auto mb-4 h-8 w-8 animate-spin rounded-full border-b-2" />
79
+ <p className="text-muted-foreground">Completing authentication...</p>
80
+ </div>
81
+ )}
82
+ </CardContent>
83
+ </Card>
84
+ );
85
+ }
86
+
87
+ function LoadingFallback() {
88
+ return (
89
+ <Card className="w-full max-w-md">
90
+ <CardContent className="pt-6">
91
+ <div className="text-center">
92
+ <div className="border-primary mx-auto mb-4 h-8 w-8 animate-spin rounded-full border-b-2" />
93
+ <p className="text-muted-foreground">Loading...</p>
94
+ </div>
95
+ </CardContent>
96
+ </Card>
97
+ );
98
+ }
99
+
100
+ export default function AuthCallbackPage() {
101
+ return (
102
+ <div className="flex min-h-screen items-center justify-center">
103
+ <Suspense fallback={<LoadingFallback />}>
104
+ <AuthCallbackContent />
105
+ </Suspense>
106
+ </div>
107
+ );
108
+ }
109
+ {%- else %}
110
+ export default function AuthCallbackPage() {
111
+ return <div>OAuth not enabled</div>;
112
+ }
113
+ {%- endif %}
@@ -0,0 +1,46 @@
1
+ {%- if cookiecutter.enable_i18n %}
2
+ import { NextIntlClientProvider } from "next-intl";
3
+ import { getMessages } from "next-intl/server";
4
+ {%- endif %}
5
+ import { notFound } from "next/navigation";
6
+ import { Providers } from "../providers";
7
+ {%- if cookiecutter.enable_i18n %}
8
+ import { locales, type Locale } from "@/i18n";
9
+ {%- endif %}
10
+
11
+ {%- if cookiecutter.enable_i18n %}
12
+ export function generateStaticParams() {
13
+ return locales.map((locale) => ({ locale }));
14
+ }
15
+ {%- endif %}
16
+
17
+ export default async function LocaleLayout({
18
+ children,
19
+ params,
20
+ }: {
21
+ children: React.ReactNode;
22
+ params: Promise<{ locale: string }>;
23
+ }) {
24
+ const { locale } = await params;
25
+
26
+ {%- if cookiecutter.enable_i18n %}
27
+ // Validate locale
28
+ if (!locales.includes(locale as Locale)) {
29
+ notFound();
30
+ }
31
+
32
+ // Get messages for the current locale
33
+ const messages = await getMessages();
34
+
35
+ return (
36
+ <Providers>
37
+ <NextIntlClientProvider messages={messages}>
38
+ {children}
39
+ </NextIntlClientProvider>
40
+ </Providers>
41
+ );
42
+ {%- else %}
43
+ // i18n disabled - just render with providers
44
+ return <Providers>{children}</Providers>;
45
+ {%- endif %}
46
+ }