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.
- fastapi_fullstack-0.1.7.dist-info/METADATA +739 -0
- fastapi_fullstack-0.1.7.dist-info/RECORD +241 -0
- fastapi_fullstack-0.1.7.dist-info/WHEEL +4 -0
- fastapi_fullstack-0.1.7.dist-info/entry_points.txt +2 -0
- fastapi_fullstack-0.1.7.dist-info/licenses/LICENSE +21 -0
- fastapi_gen/__init__.py +3 -0
- fastapi_gen/cli.py +442 -0
- fastapi_gen/config.py +356 -0
- fastapi_gen/generator.py +207 -0
- fastapi_gen/prompts.py +874 -0
- fastapi_gen/template/VARIABLES.md +276 -0
- fastapi_gen/template/cookiecutter.json +93 -0
- fastapi_gen/template/hooks/post_gen_project.py +355 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/.env.prod.example +56 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/.github/workflows/ci.yml +150 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/.gitignore +109 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/AGENTS.md +55 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/CLAUDE.md +99 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/Makefile +315 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/README.md +768 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.dockerignore +60 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.env.example +155 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/.pre-commit-config.yaml +32 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/Dockerfile +56 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/env.py +76 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/script.py.mako +30 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic/versions/.gitkeep +0 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/alembic.ini +48 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/__init__.py +3 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/admin.py +447 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/__init__.py +23 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/assistant.py +226 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/langchain_assistant.py +226 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/prompts.py +10 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/tools/__init__.py +13 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/agents/tools/datetime_tool.py +17 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/__init__.py +1 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/deps.py +541 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/exception_handlers.py +98 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/router.py +10 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/__init__.py +9 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/__init__.py +87 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/agent.py +902 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/auth.py +395 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/conversations.py +498 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/health.py +227 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/items.py +275 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/oauth.py +205 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/sessions.py +168 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/users.py +333 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/webhooks.py +477 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/routes/v1/ws.py +46 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/api/versioning.py +221 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/clients/__init__.py +14 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/clients/redis.py +88 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/__init__.py +117 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/cleanup.py +75 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/example.py +28 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/commands/seed.py +266 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/__init__.py +5 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/cache.py +23 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/config.py +267 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/csrf.py +153 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/exceptions.py +122 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/logfire_setup.py +101 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/middleware.py +99 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/oauth.py +23 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/rate_limit.py +58 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/sanitize.py +271 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/core/security.py +102 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/__init__.py +7 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/base.py +41 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/__init__.py +31 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/conversation.py +319 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/item.py +96 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/session.py +126 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/user.py +218 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/models/webhook.py +244 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/db/session.py +130 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/main.py +334 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/pipelines/__init__.py +9 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/pipelines/base.py +73 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/__init__.py +49 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/base.py +154 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/conversation.py +838 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/item.py +222 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/session.py +318 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/user.py +322 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/repositories/webhook.py +358 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/__init__.py +50 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/base.py +57 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/conversation.py +192 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/item.py +52 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/session.py +42 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/token.py +31 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/user.py +64 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/schemas/webhook.py +89 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/__init__.py +38 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/conversation.py +850 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/item.py +246 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/session.py +333 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/user.py +432 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/services/webhook.py +561 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/__init__.py +5 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/celery_app.py +64 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/taskiq_app.py +38 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/__init__.py +25 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/examples.py +106 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/schedules.py +29 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/app/worker/tasks/taskiq_examples.py +92 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/cli/__init__.py +1 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/cli/commands.py +438 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/pyproject.toml +180 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/scripts/.gitkeep +0 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/__init__.py +1 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/__init__.py +1 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_auth.py +242 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_exceptions.py +151 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_health.py +113 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_items.py +310 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/api/test_users.py +253 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/conftest.py +151 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_admin.py +890 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_agents.py +261 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_clients.py +183 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_commands.py +173 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_core.py +143 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_pipelines.py +118 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_repositories.py +181 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_security.py +124 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_services.py +363 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/backend/tests/test_worker.py +85 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.dev.yml +242 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.frontend.yml +31 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.prod.yml +435 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docker-compose.yml +241 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docs/adding_features.md +132 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docs/architecture.md +63 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docs/patterns.md +161 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/docs/testing.md +120 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.dockerignore +40 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.env.example +12 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.gitignore +45 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.prettierignore +19 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/.prettierrc +11 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/Dockerfile +44 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/README.md +648 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/auth.setup.ts +49 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/auth.spec.ts +134 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/chat.spec.ts +207 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/e2e/home.spec.ts +73 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/instrumentation.ts +14 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/messages/en.json +84 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/messages/pl.json +84 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/next.config.ts +76 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/package.json +69 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/playwright.config.ts +101 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/postcss.config.mjs +7 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(auth)/layout.tsx +11 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(auth)/login/page.tsx +5 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(auth)/register/page.tsx +5 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/chat/page.tsx +48 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/dashboard/page.tsx +99 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/layout.tsx +17 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/profile/page.tsx +152 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/auth/callback/page.tsx +113 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/layout.tsx +46 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/page.tsx +73 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/login/route.ts +58 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/logout/route.ts +24 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/me/route.ts +39 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/oauth-callback/route.ts +50 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/refresh/route.ts +54 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/auth/register/route.ts +26 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/[id]/messages/route.ts +41 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/[id]/route.ts +108 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/conversations/route.ts +73 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/api/health/route.ts +21 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/globals.css +323 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/layout.tsx +22 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/providers.tsx +29 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/index.ts +2 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/login-form.tsx +120 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/auth/register-form.tsx +153 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/chat-container.tsx +234 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/chat-input.tsx +72 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/conversation-sidebar.tsx +328 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/copy-button.tsx +46 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/index.ts +11 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/local-conversation-sidebar.tsx +295 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/markdown-content.tsx +167 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/message-item.tsx +79 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/message-list.tsx +18 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/chat/tool-call-card.tsx +79 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/icons/google-icon.tsx +32 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/icons/index.ts +3 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/language-switcher.tsx +97 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/header.tsx +65 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/index.ts +2 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/layout/sidebar.tsx +82 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/index.ts +7 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/theme-provider.tsx +53 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/theme/theme-toggle.tsx +105 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/badge.tsx +35 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/button.test.tsx +75 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/button.tsx +56 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/card.tsx +82 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/index.ts +13 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/input.tsx +21 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/label.tsx +21 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/components/ui/sheet.tsx +109 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/index.ts +7 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-auth.ts +97 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-chat.ts +203 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-conversations.ts +181 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-local-chat.ts +165 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/hooks/use-websocket.ts +105 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/i18n.ts +37 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/api-client.ts +90 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/constants.ts +39 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/server-api.ts +78 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/utils.test.ts +44 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/lib/utils.ts +44 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/middleware.ts +31 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/auth-store.test.ts +72 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/auth-store.ts +64 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/chat-sidebar-store.ts +17 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/chat-store.ts +65 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/conversation-store.ts +76 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/index.ts +9 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/local-chat-store.ts +255 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/sidebar-store.ts +17 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/stores/theme-store.ts +44 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/api.ts +27 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/auth.ts +52 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/chat.ts +83 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/conversation.ts +49 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/types/index.ts +10 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/tsconfig.json +28 -0
- fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/vitest.config.ts +36 -0
- 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,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
|
+
}
|
fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/(dashboard)/layout.tsx
ADDED
|
@@ -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 %}
|
fastapi_gen/template/{{cookiecutter.project_slug}}/frontend/src/app/[locale]/auth/callback/page.tsx
ADDED
|
@@ -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
|
+
}
|