From b838ac5d24e852732a671b4078cda639204bc70b Mon Sep 17 00:00:00 2001 From: mijauexe Date: Wed, 12 Mar 2025 11:30:48 +0100 Subject: [PATCH] Load new template properly --- core/agents/wizard.py | 194 +++-- .../info/vite_react_swagger/summary.tpl | 77 ++ core/templates/registry.py | 2 + .../tree/vite_react_swagger/.gitignore | 2 + .../vite_react_swagger/client/components.json | 23 + .../client/eslint.config.js | 31 + .../tree/vite_react_swagger/client/index.html | 13 + .../vite_react_swagger/client/package.json | 82 ++ .../client/postcss.config.js | 9 + .../client/public/favicon.ico | Bin 0 -> 662 bytes .../vite_react_swagger/client/src/App.css | 45 ++ .../vite_react_swagger/client/src/App.tsx | 26 + .../vite_react_swagger/client/src/api/api.ts | 143 ++++ .../vite_react_swagger/client/src/api/auth.ts | 44 + .../client/src/components/Footer.tsx | 13 + .../client/src/components/Header.tsx | 35 + .../client/src/components/Layout.tsx | 19 + .../client/src/components/ProtectedRoute.tsx | 15 + .../client/src/components/ui/accordion.tsx | 59 ++ .../client/src/components/ui/alert-dialog.tsx | 144 ++++ .../client/src/components/ui/alert.tsx | 62 ++ .../client/src/components/ui/aspect-ratio.tsx | 8 + .../client/src/components/ui/avatar.tsx | 53 ++ .../client/src/components/ui/badge.tsx | 39 + .../client/src/components/ui/breadcrumb.tsx | 118 +++ .../client/src/components/ui/button.tsx | 59 ++ .../client/src/components/ui/calendar.tsx | 67 ++ .../client/src/components/ui/card.tsx | 82 ++ .../client/src/components/ui/carousel.tsx | 263 ++++++ .../client/src/components/ui/chart.tsx | 368 +++++++++ .../client/src/components/ui/checkbox.tsx | 31 + .../client/src/components/ui/collapsible.tsx | 14 + .../client/src/components/ui/command.tsx | 154 ++++ .../client/src/components/ui/context-menu.tsx | 201 +++++ .../client/src/components/ui/dialog.tsx | 125 +++ .../client/src/components/ui/drawer.tsx | 121 +++ .../src/components/ui/dropdown-menu.tsx | 201 +++++ .../client/src/components/ui/form.tsx | 181 +++++ .../client/src/components/ui/hover-card.tsx | 32 + .../client/src/components/ui/input-otp.tsx | 72 ++ .../client/src/components/ui/input.tsx | 25 + .../client/src/components/ui/label.tsx | 27 + .../client/src/components/ui/menubar.tsx | 239 ++++++ .../src/components/ui/navigation-menu.tsx | 131 +++ .../client/src/components/ui/pagination.tsx | 120 +++ .../client/src/components/ui/popover.tsx | 32 + .../client/src/components/ui/progress.tsx | 31 + .../client/src/components/ui/radio-group.tsx | 45 ++ .../client/src/components/ui/resizable.tsx | 48 ++ .../client/src/components/ui/scroll-area.tsx | 49 ++ .../client/src/components/ui/select.tsx | 163 ++++ .../client/src/components/ui/separator.tsx | 32 + .../client/src/components/ui/sheet.tsx | 143 ++++ .../client/src/components/ui/sidebar.tsx | 764 ++++++++++++++++++ .../client/src/components/ui/skeleton.tsx | 18 + .../client/src/components/ui/slider.tsx | 29 + .../client/src/components/ui/sonner.tsx | 34 + .../client/src/components/ui/switch.tsx | 30 + .../client/src/components/ui/table.tsx | 120 +++ .../client/src/components/ui/tabs.tsx | 56 ++ .../client/src/components/ui/textarea.tsx | 25 + .../src/components/ui/theme-provider.tsx | 73 ++ .../client/src/components/ui/theme-toggle.tsx | 40 + .../client/src/components/ui/toast.tsx | 130 +++ .../client/src/components/ui/toaster.tsx | 36 + .../client/src/components/ui/toggle-group.tsx | 62 ++ .../client/src/components/ui/toggle.tsx | 48 ++ .../client/src/components/ui/tooltip.tsx | 33 + .../client/src/config/constants.ts | 0 .../client/src/contexts/AuthContext.tsx | 71 ++ .../client/src/hooks/useMobile.tsx | 19 + .../client/src/hooks/useToast.ts | 192 +++++ .../vite_react_swagger/client/src/index.css | 71 ++ .../client/src/lib/utils.ts | 9 + .../vite_react_swagger/client/src/main.tsx | 13 + .../client/src/pages/Login.tsx | 104 +++ .../client/src/pages/Register.tsx | 105 +++ .../client/src/vite-env.d.ts | 4 + .../client/tailwind.config.js | 92 +++ .../client/tsconfig.app.json | 35 + .../vite_react_swagger/client/tsconfig.json | 16 + .../client/tsconfig.node.json | 27 + .../vite_react_swagger/client/vite.config.ts | 24 + .../tree/vite_react_swagger/package.json | 19 + core/templates/vite_react_swagger.py | 108 +++ 85 files changed, 6617 insertions(+), 102 deletions(-) create mode 100644 core/templates/info/vite_react_swagger/summary.tpl create mode 100644 core/templates/tree/vite_react_swagger/.gitignore create mode 100644 core/templates/tree/vite_react_swagger/client/components.json create mode 100644 core/templates/tree/vite_react_swagger/client/eslint.config.js create mode 100644 core/templates/tree/vite_react_swagger/client/index.html create mode 100644 core/templates/tree/vite_react_swagger/client/package.json create mode 100644 core/templates/tree/vite_react_swagger/client/postcss.config.js create mode 100644 core/templates/tree/vite_react_swagger/client/public/favicon.ico create mode 100644 core/templates/tree/vite_react_swagger/client/src/App.css create mode 100644 core/templates/tree/vite_react_swagger/client/src/App.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/api/api.ts create mode 100644 core/templates/tree/vite_react_swagger/client/src/api/auth.ts create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/Footer.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/Header.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/Layout.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ProtectedRoute.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/accordion.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/alert-dialog.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/alert.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/aspect-ratio.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/avatar.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/badge.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/breadcrumb.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/button.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/calendar.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/card.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/carousel.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/chart.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/checkbox.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/collapsible.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/command.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/context-menu.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/dialog.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/drawer.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/dropdown-menu.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/form.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/hover-card.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/input-otp.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/input.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/label.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/menubar.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/navigation-menu.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/pagination.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/popover.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/progress.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/radio-group.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/resizable.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/scroll-area.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/select.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/separator.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/sheet.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/sidebar.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/skeleton.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/slider.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/sonner.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/switch.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/table.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/tabs.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/textarea.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/theme-provider.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/theme-toggle.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/toast.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/toaster.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/toggle-group.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/toggle.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/components/ui/tooltip.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/config/constants.ts create mode 100644 core/templates/tree/vite_react_swagger/client/src/contexts/AuthContext.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/hooks/useMobile.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/hooks/useToast.ts create mode 100644 core/templates/tree/vite_react_swagger/client/src/index.css create mode 100644 core/templates/tree/vite_react_swagger/client/src/lib/utils.ts create mode 100644 core/templates/tree/vite_react_swagger/client/src/main.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/pages/Login.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/pages/Register.tsx create mode 100644 core/templates/tree/vite_react_swagger/client/src/vite-env.d.ts create mode 100644 core/templates/tree/vite_react_swagger/client/tailwind.config.js create mode 100644 core/templates/tree/vite_react_swagger/client/tsconfig.app.json create mode 100644 core/templates/tree/vite_react_swagger/client/tsconfig.json create mode 100644 core/templates/tree/vite_react_swagger/client/tsconfig.node.json create mode 100644 core/templates/tree/vite_react_swagger/client/vite.config.ts create mode 100644 core/templates/tree/vite_react_swagger/package.json create mode 100644 core/templates/vite_react_swagger.py diff --git a/core/agents/wizard.py b/core/agents/wizard.py index be4a171d..aa212c62 100644 --- a/core/agents/wizard.py +++ b/core/agents/wizard.py @@ -1,3 +1,4 @@ +import asyncio import json import secrets from json import JSONDecodeError @@ -8,9 +9,11 @@ import httpx import yaml from core.agents.base import BaseAgent +from core.agents.frontend import FE_INIT from core.agents.response import AgentResponse from core.config import SWAGGER_EMBEDDINGS_API from core.log import get_logger +from core.telemetry import telemetry from core.templates.registry import PROJECT_TEMPLATES from core.ui.base import ProjectStage @@ -36,7 +39,7 @@ class Wizard(BaseAgent): if "components" in docs and "securitySchemes" in docs["components"]: auth_methods["types"] = [details["type"] for details in docs["components"]["securitySchemes"].values()] auth_methods["api_version"] = 3 - auth_methods["external_api_url"] = docs.get("servers", [{}])[0].get("url", "") + auth_methods["external_api_url"] = docs.get("servers", [{}])[0].get("url", "https://api.example.com") elif "securityDefinitions" in docs: auth_methods["types"] = [details["type"] for details in docs["securityDefinitions"].values()] @@ -46,123 +49,102 @@ class Wizard(BaseAgent): ) return auth_methods - def create_custom_buttons(self, auth_methods): - custom_values = { - "basic": "HTTP Basic", - "bearer": "HTTP Bearer", - "apiKey": "API Key", - "openIdConnect": "OpenID Connect", - "oauth2": "OAuth2", - } - return {method: custom_values[method] for method in auth_methods if method in custom_values} - async def run(self) -> AgentResponse: - await self.init_frontend() + while True: + if await self.init_frontend(): + break return AgentResponse.done(self) - async def init_frontend(self): + async def init_frontend(self) -> bool: """ Sets up the frontend :return: AgentResponse.done(self) """ - auth_methods = {} - options = {} - + self.next_state.action = FE_INIT await self.ui.send_project_stage({"stage": ProjectStage.PROJECT_DESCRIPTION}) - description = await self.ask_question( - "Please describe the app you want to build.", - allow_empty=False, - full_screen=True, - ) - description = description.text.strip() + self.state_manager.template = {} + options = {} if self.state_manager.project.project_type == "swagger": - while True: - try: - docs = await self.ask_question( - "Paste the OpenAPI/Swagger JSON or YAML docs here", - allow_empty=False, - verbose=True, - ) - content = self.load_docs(docs.text.strip()) - auth_methods = self.get_auth_methods(content) - - self.next_state.knowledge_base["docs"] = content - - self.next_state.knowledge_base["docs"]["api_version"] = auth_methods["api_version"] - - self.next_state.knowledge_base["docs"]["external_api_url"] = auth_methods["external_api_url"] - + if not self.next_state.knowledge_base.get("docs", None): + while True: try: - url = urljoin(SWAGGER_EMBEDDINGS_API, "upload") - async with httpx.AsyncClient( - transport=httpx.AsyncHTTPTransport(retries=3), timeout=httpx.Timeout(30.0, connect=60.0) - ) as client: - await client.post( - url, - json={ - "text": docs.text.strip(), - "project_id": str(self.state_manager.project.id), - "user_id": "1", - }, - ) + docs = await self.ask_question( + "Paste the OpenAPI/Swagger JSON or YAML docs here", + allow_empty=False, + verbose=True, + ) + content = self.load_docs(docs.text.strip()) + auth_methods = self.get_auth_methods(content) + self.next_state.knowledge_base["docs"] = content + self.next_state.knowledge_base["docs"]["api_version"] = auth_methods["api_version"] + self.next_state.knowledge_base["docs"]["external_api_url"] = auth_methods["external_api_url"] + + try: + url = urljoin(SWAGGER_EMBEDDINGS_API, "upload") + async with httpx.AsyncClient( + transport=httpx.AsyncHTTPTransport(retries=3), timeout=httpx.Timeout(30.0, connect=60.0) + ) as client: + await client.post( + url, + json={ + "text": docs.text.strip(), + "project_id": str(self.state_manager.project.id), + "user_id": "1", + }, + ) + + except Exception as e: + log.warning(f"Failed to fetch from RAG service: {e}", exc_info=True) + + break except Exception as e: - log.warning(f"Failed to fetch from RAG service: {e}", exc_info=True) + log.debug(f"An error occurred: {str(e)}") + await self.send_message("Please provide a valid input.") + continue - break - except Exception as e: - log.debug(f"An error occurred: {str(e)}") - await self.send_message("Please provide a valid input.") - continue + options["auth"] = True - if len(auth_methods) > 1: - question = "Pythagora detected multiple authentication methods in your API docs. Do you need authentication in your app (login, register, etc.)?" - elif len(auth_methods) == 1: - question = f'Pythagora detected {next(iter(self.create_custom_buttons(auth_methods["types"])))} authentication in your API docs. Do you want to use it?' - else: - question = "Pythagora didn't detect any authentication methods in your API docs. Do you need authentication in your app (login, register, etc.)?" - - auth_needed = await self.ask_question( - question, + auth_type_question = await self.ask_question( + "Which authentication method do you want to use?", buttons={ - "yes": "Yes", - "no": "No", + "apiKey": "API Key", + "basic": "HTTP Basic (coming soon)", + "bearer": "HTTP Bearer (coming soon)", + "openIdConnect": "OpenID Connect (coming soon)", + "oauth2": "OAuth2 (coming soon)", }, buttons_only=True, - default="no", + default="apiKey", ) - if auth_needed.button == "yes": - options["auth"] = True - - auth_type_question = await self.ask_question( - "Which authentication method do you want to use?", - buttons=self.create_custom_buttons(auth_methods["types"]), - buttons_only=True, - default=next(iter(self.create_custom_buttons(auth_methods["types"]))), + if auth_type_question.button == "apiKey": + api_key = await self.ask_question( + "Enter your API key here", + allow_empty=False, + verbose=True, ) - - if auth_type_question.button == "apiKey": - api_key = await self.ask_question( - "Enter your API key here", - allow_empty=False, - verbose=True, + options["auth_type"] = "api_key" + options["api_key"] = api_key.text.strip() + options["external_api_url"] = self.next_state.knowledge_base["docs"]["external_api_url"] + else: + auth_type_question = await self.ask_question( + "We are still working on getting this auth method implemented correctly. Can we contact you to get more info on how you would like it to work?", + allow_empty=False, + buttons={"yes": "Yes", "no": "No"}, + default="yes", + buttons_only=True, + ) + if auth_type_question.button == "yes": + await telemetry.trace_code_event( + "auth-method", + {"type": auth_type_question.button}, ) - options["auth_type"] = "api_key" - options["api_key"] = api_key.text.strip() - options["external_api_url"] = self.next_state.knowledge_base["docs"]["external_api_url"] - elif auth_type_question.button == "basic": - raise NotImplementedError() - elif auth_type_question.button == "bearer": - raise NotImplementedError() - elif auth_type_question.button == "openIdConnect": - raise NotImplementedError() - elif auth_type_question.button == "oauth2": - raise NotImplementedError() - + await self.send_message("Thank you for submitting your request. We will be in touch. :)") + return False else: auth_needed = await self.ask_question( "Do you need authentication in your app (login, register, etc.)?", @@ -183,7 +165,17 @@ class Wizard(BaseAgent): self.next_state.knowledge_base["user_options"] = options self.state_manager.user_options = options - await self.send_message("Setting up the project...") + if not self.state_manager.async_tasks: + self.state_manager.async_tasks = [] + self.state_manager.async_tasks.append(asyncio.create_task(self.apply_template(options))) + + description = await self.ask_question( + "Please describe the app you want to build.", + allow_empty=False, + full_screen=True, + ) + description = description.text.strip() + self.state_manager.template["description"] = description self.next_state.epics = [ { @@ -197,18 +189,16 @@ class Wizard(BaseAgent): } ] - await self.apply_template(options) - - return + return True async def apply_template(self, options: dict = {}): """ Applies a template to the frontend. """ - if options["auth_type"] == "login" or options["auth_type"] == "api_key": - template_name = "vite_react" + if options["auth_type"] == "api_key": + template_name = "vite_react_swagger" else: - raise NotImplementedError() + template_name = "vite_react" template_class = PROJECT_TEMPLATES.get(template_name) if not template_class: log.error(f"Project template not found: {template_name}") @@ -219,7 +209,7 @@ class Wizard(BaseAgent): self.state_manager, self.process_manager, ) - + self.state_manager.template["template"] = template log.info(f"Applying project template: {template.name}") summary = await template.apply() diff --git a/core/templates/info/vite_react_swagger/summary.tpl b/core/templates/info/vite_react_swagger/summary.tpl new file mode 100644 index 00000000..3f8aa603 --- /dev/null +++ b/core/templates/info/vite_react_swagger/summary.tpl @@ -0,0 +1,77 @@ +IMPORTANT: +This app has 2 parts: + +** #1 Frontend ** + * ReactJS based frontend in root folder using Vite devserver + * Integrated shadcn-ui component library with Tailwind CSS framework + * Client-side routing using `react-router-dom` with page components defined in `src/pages/` and other components in `src/components` + * It is running on port 5173 and this port should be used for user testing when possible + * All requests to the backend need to go to an endpoint that starts with `/api/` (e.g. `/api/companies`) + * Server proxy configuration is already configured and should not be changed in any way! + * Implememented pages: + * Home - home (index) page (`/`){% if options.auth %} + * Login - login page (`/login/`) - on login, stores the auth tokens to `accessToken` and `refreshToken` variables in local storage + * Register - register page (`/register/`) - on register, store **ONLY** the `accessToken` variable in local storage{% endif %} + +** IMPORTANT - Mocking data on the frontend ** +All API requests from the frontend to the backend must be defined in files inside the api folder (you must never make requests directly from the components) and the data must be mocked during the frontend implementation. Make sure to always add an API request whenever something needs to be sent or fetched from the backend. +When you add mock data, make sure to mock the data in files in the `src/api` folder and above each mocked API request, add a structure that is expected from the API with fields `Description`, `Endpoint`, `Request`, and `Response`. You **MUST NOT** add mock data anywhere else in the frontend codebase. +Mocking example: + +The base src/api/api.ts is already created so here are 2 examples for how to write functions to get data from the backend with the mock data: +—EXAMPLE_1 (file `src/api/companies.ts`) — +import api from './api'; + +// Description: Get a list of Companies +// Endpoint: GET /api/companies +// Request: {} +// Response: { companies: Array<{ domain: string, name: string, lastContact: string }> } +export const getCompanies = () => { + // Mocking the response + return new Promise((resolve) => { + setTimeout(() => { + resolve({ + companies: [ + {domain: 'google.com', name: 'Google', lastContact: '2021-08-01'}, + {domain: 'facebook.com', name: 'Facebook', lastContact: '2021-08-02'}, + {domain: 'microsoft.com', name: 'Microsoft', lastContact: '2021-08-03'}, + ], + }); + }, 500); + }); + // Uncomment the below lines to make an actual API call + // try { + // return await api.get('/api/companies', data); + // } catch (error) { + // throw new Error(error?.response?.data?.error || error.message); + // } +} +—END_OF_EXAMPLE_1— + +—EXAMPLE_2 (file `src/api/work.ts`) — +import api from './api'; + +// Description: Add a new Work +// Endpoint: POST /api/work +// Request: { work: string, driveLink: string } +// Response: { success: boolean, message: string } +export const addWork = (data: { work: string; driveLink: string }) => { + // Mocking the response + return new Promise((resolve) => { + setTimeout(() => { + resolve({success: true, message: 'Work added successfully'}); + }, 500); + }); + // Uncomment the below lines to make an actual API call + // try { + // return await api.post('/api/work/add', data); + // } catch (error) { + // throw new Error(error?.response?.data?.error || error.message); + // } +} +—END_OF_EXAMPLE_2— + +Whenever you add an API request from the frontend, make sure to wrap the request in try/catch block and in the catch block, return `throw new Error(error?.response?.data?.message || error.message);` - in the place where the API request function is being called, show a toast message with an error. + +**IMPORTANT** +Mongodb is being used as a database so whenever you need to take an `id` of an object on frontend, make sure to take `_id`. For example, if you have a company object, whenever you want to set an id for an element, you should get `company._id` instead of `company.id`. diff --git a/core/templates/registry.py b/core/templates/registry.py index 492a3b9f..b8035ab4 100644 --- a/core/templates/registry.py +++ b/core/templates/registry.py @@ -5,6 +5,7 @@ from core.log import get_logger # from .javascript_react import JavascriptReactProjectTemplate # from .node_express_mongoose import NodeExpressMongooseProjectTemplate from .vite_react import ViteReactProjectTemplate +from .vite_react_swagger import ViteReactSwaggerProjectTemplate # from .react_express import ReactExpressProjectTemplate @@ -24,5 +25,6 @@ PROJECT_TEMPLATES = { # JavascriptReactProjectTemplate.name: JavascriptReactProjectTemplate, # NodeExpressMongooseProjectTemplate.name: NodeExpressMongooseProjectTemplate, ViteReactProjectTemplate.name: ViteReactProjectTemplate, + ViteReactSwaggerProjectTemplate.name: ViteReactSwaggerProjectTemplate, # ReactExpressProjectTemplate.name: ReactExpressProjectTemplate, } diff --git a/core/templates/tree/vite_react_swagger/.gitignore b/core/templates/tree/vite_react_swagger/.gitignore new file mode 100644 index 00000000..504afef8 --- /dev/null +++ b/core/templates/tree/vite_react_swagger/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +package-lock.json diff --git a/core/templates/tree/vite_react_swagger/client/components.json b/core/templates/tree/vite_react_swagger/client/components.json new file mode 100644 index 00000000..455581be --- /dev/null +++ b/core/templates/tree/vite_react_swagger/client/components.json @@ -0,0 +1,23 @@ +{% raw %} +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "default", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "tailwind.config.js", + "css": "src/index.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "iconLibrary": "lucide" +} +{% endraw %} \ No newline at end of file diff --git a/core/templates/tree/vite_react_swagger/client/eslint.config.js b/core/templates/tree/vite_react_swagger/client/eslint.config.js new file mode 100644 index 00000000..8af0b78f --- /dev/null +++ b/core/templates/tree/vite_react_swagger/client/eslint.config.js @@ -0,0 +1,31 @@ +{% raw %} +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' + +export default tseslint.config( + { ignores: ['dist'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +) + +{% endraw %} \ No newline at end of file diff --git a/core/templates/tree/vite_react_swagger/client/index.html b/core/templates/tree/vite_react_swagger/client/index.html new file mode 100644 index 00000000..927139bb --- /dev/null +++ b/core/templates/tree/vite_react_swagger/client/index.html @@ -0,0 +1,13 @@ + + + + + + {{ project_name }} + + + +
+ + + diff --git a/core/templates/tree/vite_react_swagger/client/package.json b/core/templates/tree/vite_react_swagger/client/package.json new file mode 100644 index 00000000..5b74cf40 --- /dev/null +++ b/core/templates/tree/vite_react_swagger/client/package.json @@ -0,0 +1,82 @@ +{ + "name": "vite_client", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "start": "npm run dev", + "dev": "vite", + "build": "tsc -b && vite build", + "lint": "eslint .", + "preview": "vite preview" + }, + "dependencies": { + "@hookform/resolvers": "^3.9.1", + "@radix-ui/react-accordion": "^1.2.1", + "@radix-ui/react-alert-dialog": "^1.1.2", + "@radix-ui/react-aspect-ratio": "^1.1.0", + "@radix-ui/react-avatar": "^1.1.1", + "@radix-ui/react-checkbox": "^1.1.2", + "@radix-ui/react-collapsible": "^1.1.1", + "@radix-ui/react-context-menu": "^2.2.2", + "@radix-ui/react-dialog": "^1.1.2", + "@radix-ui/react-dropdown-menu": "^2.1.2", + "@radix-ui/react-hover-card": "^1.1.2", + "@radix-ui/react-label": "^2.1.0", + "@radix-ui/react-menubar": "^1.1.2", + "@radix-ui/react-navigation-menu": "^1.2.1", + "@radix-ui/react-popover": "^1.1.2", + "@radix-ui/react-progress": "^1.1.0", + "@radix-ui/react-radio-group": "^1.2.1", + "@radix-ui/react-scroll-area": "^1.2.1", + "@radix-ui/react-select": "^2.1.2", + "@radix-ui/react-separator": "^1.1.0", + "@radix-ui/react-slider": "^1.2.1", + "@radix-ui/react-slot": "^1.1.0", + "@radix-ui/react-switch": "^1.1.1", + "@radix-ui/react-tabs": "^1.1.1", + "@radix-ui/react-toast": "^1.2.2", + "@radix-ui/react-toggle": "^1.1.0", + "@radix-ui/react-toggle-group": "^1.1.0", + "@radix-ui/react-tooltip": "^1.1.4", + "axios": "^1.7.8", + "class-variance-authority": "^0.7.0", + "clsx": "^2.1.1", + "cmdk": "^1.0.0", + "date-fns": "^3.6.0", + "embla-carousel-react": "^8.5.1", + "input-otp": "^1.4.1", + "lucide-react": "^0.460.0", + "next-themes": "^0.4.3", + "react": "^18.3.1", + "react-day-picker": "^8.10.1", + "react-dom": "^18.3.1", + "react-hook-form": "^7.53.2", + "react-resizable-panels": "^2.1.7", + "react-router-dom": "^7.0.1", + "recharts": "^2.13.3", + "sonner": "^1.7.0", + "tailwind-merge": "^2.5.4", + "tailwindcss-animate": "^1.0.7", + "vaul": "^1.1.1", + "zod": "^3.23.8", + "json-bigint": "^1.0.0" + }, + "devDependencies": { + "@eslint/js": "^9.13.0", + "@types/node": "^22.9.1", + "@types/react": "^18.3.12", + "@types/react-dom": "^18.3.1", + "@vitejs/plugin-react": "^4.3.1", + "autoprefixer": "^10.4.20", + "eslint": "^9.13.0", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.14", + "globals": "^15.11.0", + "postcss": "^8.4.49", + "tailwindcss": "^3.4.15", + "typescript": "~5.6.2", + "typescript-eslint": "^8.11.0", + "vite": "^5.4.8" + } +} diff --git a/core/templates/tree/vite_react_swagger/client/postcss.config.js b/core/templates/tree/vite_react_swagger/client/postcss.config.js new file mode 100644 index 00000000..b41d7dd5 --- /dev/null +++ b/core/templates/tree/vite_react_swagger/client/postcss.config.js @@ -0,0 +1,9 @@ +{% raw %} +export default { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} + +{% endraw %} \ No newline at end of file diff --git a/core/templates/tree/vite_react_swagger/client/public/favicon.ico b/core/templates/tree/vite_react_swagger/client/public/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..0faa111b48b6897f1a2f18c1b0adf0c34b6b643d GIT binary patch literal 662 zcmV;H0%`q;P)Px%P)S5VR5(wClTS$0K@`WoPxtqmncr_Kf}kKsB#{nb2oZRQKo`SABVh}R6QILcf+m6;f~Y7c5OL?dn5`All?LYW<~yJF zeKYS7)>!A`QlSLt&?2fCm%2x2qL`G51>QnAZ!aXdRs~A==7R^cwGFb!n9$O1s0QYjH+yw?2Tr*t{5jniv?Ol z<>8m~u>kqUL2H|wQi5x)b|pB{_mCbrVSkfs^93J_g>MA64nC*UDnNU?=y}16VQe)P z?>ASXy0@3OAiUQoHx(llS}Yoi9kEw5>&})Ov?S$>XVGUE@E9$(hv>=jT89ouSwt}H zTxdb2evFPU0qPSHnh0hzV~e(Um${GK1d^3s_P9ar`W8vU$dy{`tbI*Wc?S(0a?WR@ zHRo$@8W^Cb#TqQTUoI%YMd#9IJlHo*$EZ0e>lO*KK4X&>ycy1*21T+DHp*U~F=8w> zI)GxZ7F1E4%MPS{M-{YXGc;Jp-rptO&(EmUjLtxCD6lBWcjq4mZVypir;PcGm0000007*qoM6N<$f& + + + + } /> + } /> + } /> + + + + + + ) +} + +export default App diff --git a/core/templates/tree/vite_react_swagger/client/src/api/api.ts b/core/templates/tree/vite_react_swagger/client/src/api/api.ts new file mode 100644 index 00000000..32eafe6b --- /dev/null +++ b/core/templates/tree/vite_react_swagger/client/src/api/api.ts @@ -0,0 +1,143 @@ +import axios, { AxiosRequestConfig, AxiosError } from 'axios'; +import JSONbig from 'json-bigint'; + +{% if options.auth_type == "api_key" %} +const API_KEY = import.meta.env.VITE_API_KEY; +const EXTERNAL_API_URL = import.meta.env.VITE_EXTERNAL_API_URL; +{% endif %} + +const localApi = axios.create({ + headers: { + 'Content-Type': 'application/json', + }, + validateStatus: (status) => { + return status >= 200 && status < 300; + }, + transformResponse: [(data) => JSONbig.parse(data)] +}); + +{% if options.auth_type != "login" %} +const externalApi = axios.create({ + baseURL: EXTERNAL_API_URL, + headers: { + 'Content-Type': 'application/json', + }, + validateStatus: (status) => { + return status >= 200 && status < 300; + }, +}); +{% endif %} + + +let accessToken: string | null = null; + +{% if options.auth %} +const isAuthEndpoint = (url: string): boolean => { + return url.includes("/api/auth"); +}; + +const getApiInstance = (url: string) => { + {% if options.auth_type != "login" %} + return isAuthEndpoint(url) ? localApi : externalApi; + {% else %} + return localApi; + {% endif %} +}; + +const setupInterceptors = (apiInstance: typeof axios) => { + apiInstance.interceptors.request.use( + (config: AxiosRequestConfig): AxiosRequestConfig => { +{% if options.auth_type == "api_key" %} + if (!isAuthEndpoint(config.url || '')) { + config.baseURL = EXTERNAL_API_URL; + if (config.headers && API_KEY) { + config.headers['api_key'] = API_KEY; + } + } +{% endif %} + + if (!accessToken) { + accessToken = localStorage.getItem('accessToken'); + } + if (accessToken && config.headers) { + config.headers.Authorization = `Bearer ${accessToken}`; + } + + return config; + }, + (error: AxiosError): Promise => Promise.reject(error) + ); + + apiInstance.interceptors.response.use( + (response) => response, + async (error: AxiosError): Promise => { + const originalRequest = error.config as AxiosRequestConfig & { _retry?: boolean }; + + if ([401, 403].includes(error.response?.status) && !originalRequest._retry) { + originalRequest._retry = true; + + try { + if (isAuthEndpoint(originalRequest.url || '')) { + const { data } = await localApi.post(`/api/auth/refresh`, { + refreshToken: localStorage.getItem('refreshToken'), + }); + accessToken = data.data.accessToken; + localStorage.setItem('accessToken', accessToken); + localStorage.setItem('refreshToken', data.data.refreshToken); + } + + if (originalRequest.headers) { + originalRequest.headers.Authorization = `Bearer ${accessToken}`; + {% if options.auth_type == "api_key" %} + if (!isAuthEndpoint(originalRequest.url || '') && API_KEY) { + originalRequest.headers['api_key'] = API_KEY; + } + {% endif %} + } + return getApiInstance(originalRequest.url || '')(originalRequest); + } catch (err) { + localStorage.removeItem('refreshToken'); + localStorage.removeItem('accessToken'); + accessToken = null; + window.location.href = '/login'; + return Promise.reject(err); + } + } + + return Promise.reject(error); + } + ); +}; + +setupInterceptors(localApi); + +{% if options.auth_type != "login" %} +setupInterceptors(externalApi); +{% endif %} + +{% endif %} + +const api = { + request: (config: AxiosRequestConfig) => { + const apiInstance = getApiInstance(config.url || ''); + return apiInstance(config); + }, + get: (url: string, config?: AxiosRequestConfig) => { + const apiInstance = getApiInstance(url); + return apiInstance.get(url, config); + }, + post: (url: string, data?: any, config?: AxiosRequestConfig) => { + const apiInstance = getApiInstance(url); + return apiInstance.post(url, data, config); + }, + put: (url: string, data?: any, config?: AxiosRequestConfig) => { + const apiInstance = getApiInstance(url); + return apiInstance.put(url, data, config); + }, + delete: (url: string, config?: AxiosRequestConfig) => { + const apiInstance = getApiInstance(url); + return apiInstance.delete(url, config); + }, +}; + +export default api; diff --git a/core/templates/tree/vite_react_swagger/client/src/api/auth.ts b/core/templates/tree/vite_react_swagger/client/src/api/auth.ts new file mode 100644 index 00000000..4f5cfa8e --- /dev/null +++ b/core/templates/tree/vite_react_swagger/client/src/api/auth.ts @@ -0,0 +1,44 @@ +{% if options.auth %} +import api from './api'; + +// Description: Login user functionality +// Endpoint: POST /api/auth/login +// Request: { email: string, password: string } +// Response: { accessToken: string, refreshToken: string } +export const login = async (email: string, password: string) => { + try { + return { accessToken: '123', refreshToken: '123' }; // pythagora_mocked_data - remove when the backend is being implemented + const response = await api.post('/api/auth/login', { email, password }); + return response.data; + } catch (error) { + console.error('Login error:', error); + throw new Error(error?.response?.data?.message || error.message); + } +}; + +// Description: Register user functionality +// Endpoint: POST /api/auth/register +// Request: { email: string, password: string } +// Response: { email: string } +export const register = async (email: string, password: string) => { + try { + return { email: 'jake@example.com' }; // pythagora_mocked_data - remove when the backend is being implemented + const response = await api.post('/api/auth/register', {email, password}); + return response.data; + } catch (error) { + throw new Error(error?.response?.data?.message || error.message); + } +}; + +// Description: Logout +// Endpoint: POST /api/auth/logout +// Request: {} +// Response: { success: boolean, message: string } +export const logout = async () => { + try { + return await api.post('/api/auth/logout'); + } catch (error) { + throw new Error(error?.response?.data?.message || error.message); + } +}; +{% endif %} diff --git a/core/templates/tree/vite_react_swagger/client/src/components/Footer.tsx b/core/templates/tree/vite_react_swagger/client/src/components/Footer.tsx new file mode 100644 index 00000000..a493341e --- /dev/null +++ b/core/templates/tree/vite_react_swagger/client/src/components/Footer.tsx @@ -0,0 +1,13 @@ +{% raw %} +export function Footer() { + return ( + + ) +} +{% endraw %} \ No newline at end of file diff --git a/core/templates/tree/vite_react_swagger/client/src/components/Header.tsx b/core/templates/tree/vite_react_swagger/client/src/components/Header.tsx new file mode 100644 index 00000000..4aaefccf --- /dev/null +++ b/core/templates/tree/vite_react_swagger/client/src/components/Header.tsx @@ -0,0 +1,35 @@ +{% raw %} +import { Bell{% endraw %}{% if options.auth %}, LogOut{% endif %}{% raw %} } from "lucide-react" +import { Button } from "./ui/button" +import { ThemeToggle } from "./ui/theme-toggle" +import { useAuth } from "@/contexts/AuthContext" +import { useNavigate } from "react-router-dom" + +export function Header() { +{% endraw %} +{% if options.auth %} + const { logout } = useAuth() +{% endif %} + const navigate = useNavigate() +{% if options.auth %} + const handleLogout = () => { + logout() + navigate("/login") + } +{% endif %} + return ( +
+
+
Home
+
+ + {% if options.auth %} + + {% endif %} +
+
+
+ ) +} diff --git a/core/templates/tree/vite_react_swagger/client/src/components/Layout.tsx b/core/templates/tree/vite_react_swagger/client/src/components/Layout.tsx new file mode 100644 index 00000000..008a7405 --- /dev/null +++ b/core/templates/tree/vite_react_swagger/client/src/components/Layout.tsx @@ -0,0 +1,19 @@ +import { Outlet } from "react-router-dom" +import { Header } from "./Header" +import { Footer } from "./Footer" + +export function Layout() { + return ( +
+
+
+
+
+ +
+
+
+
+
+ ) +} diff --git a/core/templates/tree/vite_react_swagger/client/src/components/ProtectedRoute.tsx b/core/templates/tree/vite_react_swagger/client/src/components/ProtectedRoute.tsx new file mode 100644 index 00000000..8f18cff3 --- /dev/null +++ b/core/templates/tree/vite_react_swagger/client/src/components/ProtectedRoute.tsx @@ -0,0 +1,15 @@ +{% raw %} +import { Navigate, useLocation } from "react-router-dom"; +import { useAuth } from "@/contexts/AuthContext"; + +export function ProtectedRoute({ children }: { children: React.ReactNode }) { + const { isAuthenticated } = useAuth(); + const location = useLocation(); + + if (!isAuthenticated) { + return ; + } + + return <>{children}; +} +{% endraw %} diff --git a/core/templates/tree/vite_react_swagger/client/src/components/ui/accordion.tsx b/core/templates/tree/vite_react_swagger/client/src/components/ui/accordion.tsx new file mode 100644 index 00000000..2df7e37d --- /dev/null +++ b/core/templates/tree/vite_react_swagger/client/src/components/ui/accordion.tsx @@ -0,0 +1,59 @@ +{% raw %} +import * as React from "react" +import * as AccordionPrimitive from "@radix-ui/react-accordion" +import { ChevronDown } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Accordion = AccordionPrimitive.Root + +const AccordionItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AccordionItem.displayName = "AccordionItem" + +const AccordionTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + + svg]:rotate-180", + className + )} + {...props} + > + {children} + + + +)) +AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName + +const AccordionContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, children, ...props }, ref) => ( + +
{children}
+
+)) + +AccordionContent.displayName = AccordionPrimitive.Content.displayName + +export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } + +{% endraw %} \ No newline at end of file diff --git a/core/templates/tree/vite_react_swagger/client/src/components/ui/alert-dialog.tsx b/core/templates/tree/vite_react_swagger/client/src/components/ui/alert-dialog.tsx new file mode 100644 index 00000000..8ef336c2 --- /dev/null +++ b/core/templates/tree/vite_react_swagger/client/src/components/ui/alert-dialog.tsx @@ -0,0 +1,144 @@ +{% raw %} +"use client" + +import * as React from "react" +import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog" + +import { cn } from "@/lib/utils" +import { buttonVariants } from "@/components/ui/button" + +const AlertDialog = AlertDialogPrimitive.Root + +const AlertDialogTrigger = AlertDialogPrimitive.Trigger + +const AlertDialogPortal = AlertDialogPrimitive.Portal + +const AlertDialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName + +const AlertDialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + + + + +)) +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName + +const AlertDialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +AlertDialogHeader.displayName = "AlertDialogHeader" + +const AlertDialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+) +AlertDialogFooter.displayName = "AlertDialogFooter" + +const AlertDialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName + +const AlertDialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogDescription.displayName = + AlertDialogPrimitive.Description.displayName + +const AlertDialogAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName + +const AlertDialogCancel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +} + +{% endraw %} \ No newline at end of file diff --git a/core/templates/tree/vite_react_swagger/client/src/components/ui/alert.tsx b/core/templates/tree/vite_react_swagger/client/src/components/ui/alert.tsx new file mode 100644 index 00000000..c2152292 --- /dev/null +++ b/core/templates/tree/vite_react_swagger/client/src/components/ui/alert.tsx @@ -0,0 +1,62 @@ +{% raw %} +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const alertVariants = cva( + "relative w-full rounded-lg border p-4 [&>svg~*]:pl-7 [&>svg+div]:translate-y-[-3px] [&>svg]:absolute [&>svg]:left-4 [&>svg]:top-4 [&>svg]:text-foreground", + { + variants: { + variant: { + default: "bg-background text-foreground", + destructive: + "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +const Alert = React.forwardRef< + HTMLDivElement, + React.HTMLAttributes & VariantProps +>(({ className, variant, ...props }, ref) => ( +
+)) +Alert.displayName = "Alert" + +const AlertTitle = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +AlertTitle.displayName = "AlertTitle" + +const AlertDescription = React.forwardRef< + HTMLParagraphElement, + React.HTMLAttributes +>(({ className, ...props }, ref) => ( +
+)) +AlertDescription.displayName = "AlertDescription" + +export { Alert, AlertTitle, AlertDescription } + +{% endraw %} \ No newline at end of file diff --git a/core/templates/tree/vite_react_swagger/client/src/components/ui/aspect-ratio.tsx b/core/templates/tree/vite_react_swagger/client/src/components/ui/aspect-ratio.tsx new file mode 100644 index 00000000..80e9fd0d --- /dev/null +++ b/core/templates/tree/vite_react_swagger/client/src/components/ui/aspect-ratio.tsx @@ -0,0 +1,8 @@ +{% raw %} +import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio" + +const AspectRatio = AspectRatioPrimitive.Root + +export { AspectRatio } + +{% endraw %} \ No newline at end of file diff --git a/core/templates/tree/vite_react_swagger/client/src/components/ui/avatar.tsx b/core/templates/tree/vite_react_swagger/client/src/components/ui/avatar.tsx new file mode 100644 index 00000000..242be94c --- /dev/null +++ b/core/templates/tree/vite_react_swagger/client/src/components/ui/avatar.tsx @@ -0,0 +1,53 @@ +{% raw %} +"use client" + +import * as React from "react" +import * as AvatarPrimitive from "@radix-ui/react-avatar" + +import { cn } from "@/lib/utils" + +const Avatar = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +Avatar.displayName = AvatarPrimitive.Root.displayName + +const AvatarImage = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarImage.displayName = AvatarPrimitive.Image.displayName + +const AvatarFallback = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({ className, ...props }, ref) => ( + +)) +AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName + +export { Avatar, AvatarImage, AvatarFallback } + +{% endraw %} \ No newline at end of file diff --git a/core/templates/tree/vite_react_swagger/client/src/components/ui/badge.tsx b/core/templates/tree/vite_react_swagger/client/src/components/ui/badge.tsx new file mode 100644 index 00000000..1bb2e513 --- /dev/null +++ b/core/templates/tree/vite_react_swagger/client/src/components/ui/badge.tsx @@ -0,0 +1,39 @@ +{% raw %} +import * as React from "react" +import { cva, type VariantProps } from "class-variance-authority" + +import { cn } from "@/lib/utils" + +const badgeVariants = cva( + "inline-flex items-center rounded-full 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", + { + variants: { + variant: { + default: + "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", + secondary: + "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", + destructive: + "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", + outline: "text-foreground", + }, + }, + defaultVariants: { + variant: "default", + }, + } +) + +export interface BadgeProps + extends React.HTMLAttributes, + VariantProps {} + +function Badge({ className, variant, ...props }: BadgeProps) { + return ( +
+ ) +} + +export { Badge, badgeVariants } + +{% endraw %} \ No newline at end of file diff --git a/core/templates/tree/vite_react_swagger/client/src/components/ui/breadcrumb.tsx b/core/templates/tree/vite_react_swagger/client/src/components/ui/breadcrumb.tsx new file mode 100644 index 00000000..909c8bbf --- /dev/null +++ b/core/templates/tree/vite_react_swagger/client/src/components/ui/breadcrumb.tsx @@ -0,0 +1,118 @@ +{% raw %} +import * as React from "react" +import { Slot } from "@radix-ui/react-slot" +import { ChevronRight, MoreHorizontal } from "lucide-react" + +import { cn } from "@/lib/utils" + +const Breadcrumb = React.forwardRef< + HTMLElement, + React.ComponentPropsWithoutRef<"nav"> & { + separator?: React.ReactNode + } +>(({ ...props }, ref) =>