├── .cursorrules ├── .devcontainer └── devcontainer.json ├── .editorconfig ├── .github ├── CODE_OF_CONDUCT.md ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── pull_request_template.md ├── .gitignore ├── .qoder └── repowiki │ └── en │ ├── content │ ├── Advanced Topics │ │ ├── Advanced Topics.md │ │ ├── Creating Custom Widgets.md │ │ ├── Extending Data Providers.md │ │ └── Performance Optimization.md │ ├── Component Architecture │ │ ├── Base UI Components.md │ │ ├── Component Architecture.md │ │ └── Trading Widgets System │ │ │ ├── Chart Widget.md │ │ │ ├── Deals Widget.md │ │ │ ├── Market Data Widget.md │ │ │ ├── OrderBook Widget.md │ │ │ ├── Trades Widget.md │ │ │ ├── Trading Widgets System.md │ │ │ ├── User Balances Widget.md │ │ │ └── User Trading Data Widget.md │ ├── Core Features │ │ ├── Core Features.md │ │ ├── Dashboard Management.md │ │ ├── Notification System.md │ │ ├── Trading Functionality.md │ │ └── User Account Management.md │ ├── Data Flow & Integration │ │ ├── CCXT Integration.md │ │ ├── Data Flow & Integration.md │ │ ├── Market Data Acquisition.md │ │ └── WebSocket vs REST Strategies.md │ ├── Development Guide.md │ ├── Getting Started Guide.md │ ├── Project Overview.md │ ├── State Management │ │ ├── Dashboard Store.md │ │ ├── Data Provider Store.md │ │ ├── State Management.md │ │ ├── User Store.md │ │ └── Widget-Specific Stores.md │ ├── Technology Stack & Dependencies.md │ └── Theming & Styling │ │ ├── Styling Implementation.md │ │ ├── Theme Customization.md │ │ └── Theming & Styling.md │ └── meta │ └── repowiki-metadata.json ├── CCXT_EXPRESS_PROVIDER.md ├── CCXT_SERVER_WIDGET_INTEGRATION.md ├── CHANGELOG.md ├── CONTRIBUTING.md ├── CRUSH.md ├── KEYS.md ├── MARKETS.md ├── PRIVACY_POLICY.md ├── REACT_VS_VUE.md ├── README.md ├── ROADMAP.md ├── TERMS_OF_USE.md ├── VOCABULARY.md ├── bun.lockb ├── components.json ├── debug_bingx_balance.html ├── debug_bybit_api.html ├── debug_orders.html ├── demo.png ├── eslint.config.js ├── express.ts ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── apple-touch-icon.png ├── favicon-96x96.png ├── favicon.ico ├── favicon.svg ├── og-image.png ├── placeholder.svg ├── site.webmanifest ├── web-app-manifest-192x192.png └── web-app-manifest-512x512.png ├── src ├── App.tsx ├── components │ ├── AlignmentGuides.tsx │ ├── AnimatedLogo.tsx │ ├── BottomLeftInfo.tsx │ ├── CollapsedWidgetsZone.tsx │ ├── DebugBingXWidget.tsx │ ├── DebugCCXTCache.tsx │ ├── DebugUserData.tsx │ ├── ExchangesWidget.tsx │ ├── MarketsWidget.tsx │ ├── NotificationHistory.tsx │ ├── NotificationTestWidget.tsx │ ├── PairsWidget.tsx │ ├── RightClickInfo.tsx │ ├── SettingsDrawer.tsx │ ├── TabNavigation.tsx │ ├── TestCCXTServerProvider.tsx │ ├── TestChartWidget.tsx │ ├── TestDebugWidgetCCXTServer.tsx │ ├── TestProviderIntegration.tsx │ ├── TestServerProvider.tsx │ ├── TestTimeframes.tsx │ ├── UserDrawer.tsx │ ├── WidgetMenu.tsx │ ├── WidgetSettingsManager.tsx │ ├── WidgetSimple.tsx │ ├── ui │ │ ├── ErrorBoundary.tsx │ │ ├── GroupColorSelector.tsx │ │ ├── GroupSelector.tsx │ │ ├── InstrumentHeaderControl.tsx │ │ ├── InstrumentSearch.tsx │ │ ├── InstrumentSelector.tsx │ │ ├── SearchableSelect.tsx │ │ ├── SelectorDemo.tsx │ │ ├── TimeframeSelect.tsx │ │ ├── accordion.tsx │ │ ├── alert-dialog.tsx │ │ ├── alert.tsx │ │ ├── aspect-ratio.tsx │ │ ├── avatar.tsx │ │ ├── badge.tsx │ │ ├── breadcrumb.tsx │ │ ├── button.tsx │ │ ├── calendar.tsx │ │ ├── card.tsx │ │ ├── carousel.tsx │ │ ├── chart.tsx │ │ ├── checkbox.tsx │ │ ├── collapsible.tsx │ │ ├── command.tsx │ │ ├── context-menu.tsx │ │ ├── cookie-notification.tsx │ │ ├── dialog.tsx │ │ ├── drawer.tsx │ │ ├── dropdown-menu.tsx │ │ ├── form.tsx │ │ ├── hover-card.tsx │ │ ├── index.ts │ │ ├── input-otp.tsx │ │ ├── input.tsx │ │ ├── label.tsx │ │ ├── menubar.tsx │ │ ├── navigation-menu.tsx │ │ ├── pagination.tsx │ │ ├── popover.tsx │ │ ├── progress.tsx │ │ ├── radio-group.tsx │ │ ├── resizable.tsx │ │ ├── scroll-area.tsx │ │ ├── select.tsx │ │ ├── separator.tsx │ │ ├── sheet.tsx │ │ ├── sidebar.tsx │ │ ├── skeleton.tsx │ │ ├── slider.tsx │ │ ├── sonner.tsx │ │ ├── switch.tsx │ │ ├── table.tsx │ │ ├── tabs.tsx │ │ ├── textarea.tsx │ │ ├── toast.tsx │ │ ├── toaster.tsx │ │ ├── toggle-group.tsx │ │ ├── toggle.tsx │ │ ├── tooltip.tsx │ │ └── use-toast.ts │ └── widgets │ │ ├── Chart.tsx │ │ ├── ChartSettings.tsx │ │ ├── ChartSettingsWrapper.tsx │ │ ├── DataProviderDebugWidget.tsx │ │ ├── DataProviderDemoWidget.tsx │ │ ├── DataProviderSettingsWidget.tsx │ │ ├── DataProviderSetupWidget.tsx │ │ ├── DealDetails.tsx │ │ ├── DealsList.tsx │ │ ├── DealsWidget.tsx │ │ ├── DealsWidgetExample.tsx │ │ ├── MarketDataWidget.tsx │ │ ├── MyTradesWidget.tsx │ │ ├── OrderBookSettingsWrapper.tsx │ │ ├── OrderBookWidget.tsx │ │ ├── OrderForm.tsx │ │ ├── Portfolio.tsx │ │ ├── README.md │ │ ├── TradesSettingsWrapper.tsx │ │ ├── TradesWidget.tsx │ │ ├── TransactionHistory.tsx │ │ ├── UserBalancesPieChart.tsx │ │ ├── UserBalancesSettings.tsx │ │ ├── UserBalancesSettingsWrapper.tsx │ │ ├── UserBalancesWidget.tsx │ │ ├── UserOrdersTab.tsx │ │ ├── UserPositionsTab.tsx │ │ ├── UserTradesTab.tsx │ │ ├── UserTradingDataSettings.tsx │ │ ├── UserTradingDataSettingsWrapper.tsx │ │ ├── UserTradingDataWidget.tsx │ │ └── index.ts ├── context │ └── WidgetContext.tsx ├── hooks │ ├── use-mobile.tsx │ ├── use-toast.ts │ ├── useAlignmentGuides.tsx │ ├── useDataProvider.ts │ ├── useExchangesList.ts │ ├── useRequestLogger.ts │ ├── useRightClickInfo.ts │ ├── useTheme.tsx │ └── useWidgetDrag.tsx ├── index.css ├── lib │ └── utils.ts ├── main.tsx ├── pages │ ├── Index.tsx │ ├── NotFound.tsx │ └── TradingTerminal.tsx ├── services │ └── orderExecutionService.ts ├── store │ ├── README.md │ ├── actions │ │ ├── ccxtActions.ts │ │ ├── dataActions.ts │ │ ├── eventActions.ts │ │ ├── fetchingActions.ts │ │ ├── providerActions.ts │ │ └── subscriptionActions.ts │ ├── chartWidgetStore.ts │ ├── dashboardStore.ts │ ├── dataProviderStore.ts │ ├── groupStore.ts │ ├── notificationStore.ts │ ├── orderBookWidgetStore.ts │ ├── placeOrderStore.ts │ ├── providers │ │ ├── ccxtBrowserProvider.ts │ │ └── ccxtServerProvider.ts │ ├── settingsDrawerStore.ts │ ├── tradesWidgetStore.ts │ ├── types.ts │ ├── userBalancesWidgetStore.ts │ ├── userStore.ts │ ├── userTradingDataWidgetStore.ts │ └── utils │ │ ├── ccxtAccountManager.ts │ │ ├── ccxtInstanceManager.ts │ │ ├── ccxtProviderUtils.ts │ │ ├── ccxtUtils.ts │ │ ├── providerUtils.ts │ │ └── webSocketUtils.ts ├── types │ ├── alignmentGuides.ts │ ├── chart.ts │ ├── dashboard.ts │ ├── dataProviders.ts │ ├── deals.ts │ ├── groups.ts │ ├── nightvision.d.ts │ └── orders.ts ├── utils │ ├── alignmentGuideUtils.ts │ ├── exchangeLimits.ts │ ├── formatters.test.ts │ ├── formatters.ts │ ├── generateDemoData.ts │ └── requestLogger.ts └── vite-env.d.ts ├── structure.png ├── tailwind.config.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json ├── tsconfig.server.json ├── vercel.json └── vite.config.ts /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // The Dev Container format allows you to configure your environment. At the heart of it 2 | // is a Docker image or Dockerfile which controls the tools available in your environment. 3 | // 4 | // See https://aka.ms/devcontainer.json for more information. 5 | { 6 | "name": "Ona", 7 | // This universal image (~10GB) includes many development tools and languages, 8 | // providing a convenient all-in-one development environment. 9 | // 10 | // This image is already available on remote runners for fast startup. On desktop 11 | // and linux runners, it will need to be downloaded, which may take longer. 12 | // 13 | // For faster startup on desktop/linux, consider a smaller, language-specific image: 14 | // • For Python: mcr.microsoft.com/devcontainers/python:3.13 15 | // • For Node.js: mcr.microsoft.com/devcontainers/javascript-node:24 16 | // • For Go: mcr.microsoft.com/devcontainers/go:1.24 17 | // • For Java: mcr.microsoft.com/devcontainers/java:21 18 | // 19 | // Browse more options at: https://hub.docker.com/r/microsoft/devcontainers 20 | // or build your own using the Dockerfile option below. 21 | "image": "mcr.microsoft.com/devcontainers/universal:3.0.3" 22 | // Use "build": 23 | // instead of the image to use a Dockerfile to build an image. 24 | // "build": { 25 | // "context": ".", 26 | // "dockerfile": "Dockerfile" 27 | // } 28 | // Features add additional features to your environment. See https://containers.dev/features 29 | // Beware: features are not supported on all platforms and may have unintended side-effects. 30 | // "features": { 31 | // "ghcr.io/devcontainers/features/docker-in-docker": { 32 | // "moby": false 33 | // } 34 | // } 35 | } 36 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_size = 2 7 | indent_style = space 8 | charset = utf-8 9 | 10 | [*.py] 11 | indent_size = 4 12 | -------------------------------------------------------------------------------- /.github/CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | In the interest of fostering an open and welcoming environment, we as 6 | contributors and maintainers pledge to making participation in our project and 7 | our community a harassment-free experience for everyone, regardless of age, body 8 | size, disability, ethnicity, gender identity and expression, level of experience, 9 | education, socio-economic status, nationality, personal appearance, race, 10 | religion, or sexual identity and orientation. 11 | 12 | ## Our Standards 13 | 14 | Examples of behavior that contributes to creating a positive environment 15 | include: 16 | 17 | * Using welcoming and inclusive language 18 | * Being respectful of differing viewpoints and experiences 19 | * Gracefully accepting constructive criticism 20 | * Focusing on what is best for the community 21 | * Showing empathy towards other community members 22 | 23 | Examples of unacceptable behavior by participants include: 24 | 25 | * The use of sexualized language or imagery and unwelcome sexual attention or 26 | advances 27 | * Trolling, insulting/derogatory comments, and personal or political attacks 28 | * Public or private harassment 29 | * Publishing others' private information, such as a physical or electronic 30 | address, without explicit permission 31 | * Other conduct which could reasonably be considered inappropriate in a 32 | professional setting 33 | 34 | ## Our Responsibilities 35 | 36 | Project maintainers are responsible for clarifying the standards of acceptable 37 | behavior and are expected to take appropriate and fair corrective action in 38 | response to any instances of unacceptable behavior. 39 | 40 | Project maintainers have the right and responsibility to remove, edit, or 41 | reject comments, commits, code, wiki edits, issues, and other contributions 42 | that are not aligned to this Code of Conduct, or to ban temporarily or 43 | permanently any contributor for other behaviors that they deem inappropriate, 44 | threatening, offensive, or harmful. 45 | 46 | ## Scope 47 | 48 | This Code of Conduct applies both within project spaces and in public spaces 49 | when an individual is representing the project or its community. Examples of 50 | representing a project or community include using an official project e-mail 51 | address, posting via an official social media account, or acting as an appointed 52 | representative at an online or offline event. Representation of a project may be 53 | further defined and clarified by project maintainers. 54 | 55 | ## Enforcement 56 | 57 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 58 | reported by contacting the project team at info@kupi.network. All 59 | complaints will be reviewed and investigated and will result in a response that 60 | is deemed necessary and appropriate to the circumstances. The project team is 61 | obligated to maintain confidentiality with regard to the reporter of an incident. 62 | Further details of specific enforcement policies may be posted separately. 63 | 64 | Project maintainers who do not follow or enforce the Code of Conduct in good 65 | faith may face temporary or permanent repercussions as determined by other 66 | members of the project's leadership. 67 | 68 | ## Attribution 69 | 70 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, 71 | available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html 72 | 73 | [homepage]: https://www.contributor-covenant.org 74 | 75 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | patreon: profitmaker 2 | open_collective: profitmaker 3 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | Please check the following: 2 | 3 | 1. Make sure you are targeting the `master` branch, pull requests on release branches are only allowed for bug fixes. 4 | 2. Read contributing guidelines: https://github.com/kupi-network/kupi-terminal/blob/master/CONTRIBUTING.md 5 | 3. Describe what your pull request does and which issue you're targeting (if any) 6 | 7 | **You MUST delete the content above including this line before posting, otherwise your pull request will be invalid.** 8 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tasks/ 2 | .crush/ 3 | 4 | # Logs 5 | logs 6 | *.log 7 | npm-debug.log* 8 | yarn-debug.log* 9 | yarn-error.log* 10 | pnpm-debug.log* 11 | lerna-debug.log* 12 | 13 | node_modules 14 | dist 15 | dist-ssr 16 | *.local 17 | 18 | # Editor directories and files 19 | .vscode/* 20 | !.vscode/extensions.json 21 | .idea 22 | .DS_Store 23 | *.suo 24 | *.ntvs* 25 | *.njsproj 26 | *.sln 27 | *.sw? 28 | 29 | .env 30 | -------------------------------------------------------------------------------- /.qoder/repowiki/en/content/Getting Started Guide.md: -------------------------------------------------------------------------------- 1 | 2 | # Getting Started Guide 3 | 4 | 5 | **Referenced Files in This Document ** 6 | - [README.md](file://README.md) 7 | - [package.json](file://package.json) 8 | - [vite.config.ts](file://vite.config.ts) 9 | - [src/main.tsx](file://src/main.tsx) 10 | - [src/App.tsx](file://src/App.tsx) 11 | - [src/pages/TradingTerminal.tsx](file://src/pages/TradingTerminal.tsx) 12 | - [src/pages/Index.tsx](file://src/pages/Index.tsx) 13 | - [KEYS.md](file://KEYS.md) 14 | - [express.ts](file://express.ts) 15 | - [CCXT_EXPRESS_PROVIDER.md](file://CCXT_EXPRESS_PROVIDER.md) 16 | - [CCXT_SERVER_WIDGET_INTEGRATION.md](file://CCXT_SERVER_WIDGET_INTEGRATION.md) 17 | - [src/components/widgets/DataProviderSetupWidget.tsx](file://src/components/widgets/DataProviderSetupWidget.tsx) 18 | - [src/components/widgets/DataProviderDebugWidget.tsx](file://src/components/widgets/DataProviderDebugWidget.tsx) 19 | 20 | 21 | ## Table of Contents 22 | 1. [Introduction](#introduction) 23 | 2. [Prerequisites and Installation](#prerequisites-and-installation) 24 | 3. [Environment Configuration](#environment-configuration) 25 | 4. [Running the Development Server](#running-the-development-server) 26 | 5. [Application Interface Overview](#application-interface-overview) 27 | 6. [Entry Point Architecture](#entry-point-architecture) 28 | 7. [Common Setup Issues and Troubleshooting](#common-setup-issues-and-troubleshooting) 29 | 30 | ## Introduction 31 | 32 | The Profitmaker application is an open-source cryptocurrency trading terminal designed to support over 100 exchanges while ensuring user security by keeping API keys on the local machine. The platform allows for customizable dashboards, extendable widgets, and integration with real-time data via CCXT. This guide provides comprehensive instructions for setting up, configuring, and running the Profitmaker application in a development environment. 33 | 34 | The core architecture leverages React with Vite for fast development builds, integrates with a custom Express server for bypassing CORS restrictions, and supports both browser-based and server-side CCXT operations. Users can create personalized trading interfaces by adding widgets such as charts, order books, trade history, and balance displays. 35 | 36 | This document walks through the complete setup process from installation to initial usage, including configuration of exchange connectivity, launching the development server, navigating the interface, understanding the entry point flow, and resolving common issues encountered during setup. 37 | 38 | **Section sources** 39 | - [README.md](file://README.md#L0-L149) 40 | 41 | ## Prerequisites and Installation 42 | 43 | To set up the Profitmaker application, ensure your system meets the following prerequisites: 44 | 45 | - **Node.js**: Version 18 or higher 46 | - **npm**: Package manager bundled with Node.js 47 | - **Git**: For cloning the repository (if not already downloaded) 48 | 49 | ### Step-by-Step Installation 50 | 51 | 1. **Clone the Repository** 52 | If you haven't already obtained the codebase: 53 | ```bash 54 | git clone https://github.com/suenot/profitmaker.git 55 | cd profitmaker 56 | ``` 57 | 58 | 2. **Install Dependencies** 59 | Run the following command to install all required packages listed in `package.json`: 60 | ```bash 61 | npm install 62 | ``` 63 | This installs essential libraries including: 64 | - React and ReactDOM for UI rendering 65 | - Vite for development server and build tooling 66 | - CCXT for cryptocurrency exchange connectivity 67 | - Tailwind CSS for styling 68 | - Zustand for state management 69 | - Radix UI components for accessible UI elements 70 | 71 | 3. **Verify Installation** 72 | After installation completes, confirm that `node_modules` has been created and dependencies are correctly installed by checking for key directories like `react`, `ccxt`, and `vite`. 73 | 74 | The project uses modern JavaScript tooling with TypeScript support, so no additional compilation steps are needed before starting the development server. 75 | 76 | **Section sources** 77 | - [package.json](file://package.json#L0-L108) 78 | - [README.md](file://README.md#L110-L149) 79 | 80 | ## Environment Configuration 81 | 82 | Configuring environment variables is crucial for connecting to cryptocurrency exchanges securely -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelogs 2 | 3 | ## vue [0.3.0](https://github.com/kupi-network/kupi-terminal/commit/6369561c9c4dd86d2409e16bb18f2bd3987aeac2) (29 May 2019) 4 | 5 | - Accounting alpha (data only in localstorage) 6 | 7 | ## [0.6.0](https://github.com/kupi-network/kupi-terminal/commit/8672eb94d883efed5aa689cca8bac4ba9d7c3797) (7 May 2019) 8 | 9 | - Change structure to monorepo 10 | 11 | ## [0.5.0](https://github.com/kupi-network/kupi-terminal/commit/395dc197d87f88476f5acc727b6da6cb9870ac50) (27 Apr 2019) 12 | 13 | - Https api 14 | - Refactoring apis in express 15 | - Local authorization 16 | - Fix in catchHead() 17 | - Get public data from both sources: kupi.network api and local server (ccxt) 18 | - Refactoring mobx: move fetch and save data from mobx to widgets states 19 | 20 | ## vue [0.2.0](https://github.com/kupi-network/kupi-terminal/commit/f52f4e845abe0dd3652fd6a103b88dc3d935e8cd) (7 Apr 2019) 21 | 22 | - Electron alpha version (without keys) 23 | - Signals views 24 | 25 | ## vue [0.1.4](https://github.com/kupi-network/kupi-terminal/commit/f52f4e845abe0dd3652fd6a103b88dc3d935e8cd) (7 Apr 2019) 26 | 27 | - All base functional is ready 28 | 29 | ## [0.4.0](https://github.com/kupi-network/kupi-terminal/commit/710eb97731ad0512b8357992411e0bde488f11f7) (8 Mar 2019) 30 | 31 | - Docker-compose 32 | - Refactoring settings pages 33 | - Refactoring selector widget 34 | - Refactoring asides (before there was only one aside from left side, now there are 2 from left side, 2 from right side) 35 | - Paddingless style 36 | - Widget header styles 37 | - Swagger api 38 | - Perfect-scrollbar 39 | 40 | ## [0.3.0](https://github.com/kupi-network/kupi-terminal/commit/765373e3f5922897dfcc87231275122248a540f2) (17 Nov 2018) 41 | 42 | - Refactoring mobx: support multiple stock and pair on page 43 | 44 | ## [0.2.4](https://github.com/kupi-network/kupi-terminal/commit/eda241b300487056f2a91610f3505f6513849c51) (14 Nov 2018) 45 | 46 | - Select type in CreateOrder widget 47 | - Select total for Balances widgets 48 | - Select timeframes in candles widget 49 | 50 | ## [0.2.3](https://github.com/kupi-network/kupi-terminal/commit/91a3d5ae299ac7175b55fe4bde63911abd10d9cc) (8 Nov 2018) 51 | 52 | - Auto connect widgets from file system 53 | - Fullscreen for widgets 54 | 55 | ## [0.2.2](https://github.com/kupi-network/kupi-terminal/commit/8e98c854ec50e62aefe36d37438c2a09b233c811) (1 Nov 2018) 56 | 57 | - Icons list 58 | - Fix removing dashboards 59 | 60 | ## [0.2.1](https://github.com/kupi-network/kupi-terminal/commit/661d57d5c7cccf91ab3689da288af3669c3daf88) (18 Oct 2018) 61 | 62 | - Notes widget 63 | - Iframe widget 64 | 65 | ## [0.2.0](https://github.com/kupi-network/kupi-terminal/commit/ce134a413e2c910d100278f434ef90766ccb5b8b) (10 Oct 2018) 66 | 67 | - Widget market 68 | - Refactoring stores 69 | - Dashboard settings 70 | 71 | ## [0.1.0](https://github.com/kupi-network/kupi-terminal/commit/fa4a1ebe1fd98e17e2e67a0d58aa938eb602fcd2) (24 Sep 2018) 72 | 73 | - Alpha version. All base functional is ready 74 | 75 | ## [0.0.1](https://github.com/kupi-network/kupi-terminal/commit/2142d888aac55890e19d874c8073b0a81feeb80f) (17 Aug 2018) 76 | 77 | - Initial release 78 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing To The Kupi Terminal 2 | 3 | ```diff 4 | - This file is a work in progress, guidelines for contributing are being developed right now! 5 | ``` 6 | 7 | ## Reporting Vulnerabilities And Critical Issues 8 | 9 | If you found a security issue or a critical vulnerability and reporting it in public would impose risk – please feel free to send us a message to [info@kupi.network](mailto:info@kupi.network). 10 | -------------------------------------------------------------------------------- /CRUSH.md: -------------------------------------------------------------------------------- 1 | # CRUSH Configuration for ProfitMaker 2 | 3 | ## Build Commands 4 | - `npm run dev` - Start development server 5 | - `npm run build` - Build for production 6 | - `npm run build:dev` - Build for development 7 | - `npm run preview` - Preview production build 8 | 9 | ## Server Commands 10 | - `npm run server` - Start the CCXT server 11 | - `npm run server:dev` - Start the CCXT server in watch mode 12 | 13 | ## Linting and Type Checking 14 | - `npm run lint` - Run ESLint on the codebase 15 | - Type checking is handled by TypeScript (configured in tsconfig files) 16 | 17 | ## Testing 18 | - No test framework configured yet 19 | - To add tests, consider using Jest or Vitest 20 | - Run individual test files with: `npm test -- ` 21 | 22 | ## Code Style Guidelines 23 | 24 | ### Imports 25 | - Use absolute imports with `@/*` alias for src directory 26 | - Group imports in order: built-in, external, internal, type imports 27 | - Use named imports when possible 28 | 29 | ### Formatting 30 | - Follow ESLint rules defined in eslint.config.js 31 | - Use Prettier for code formatting (implied by project setup) 32 | - 2-space indentation 33 | - No trailing commas in function parameters 34 | - Semicolons are optional (follow existing code style) 35 | 36 | ### Types 37 | - Use TypeScript for all files 38 | - Prefer interfaces over types for object shapes 39 | - Use explicit typing when it improves clarity 40 | - Leverage type inference when possible 41 | 42 | ### Naming Conventions 43 | - Use PascalCase for components and types 44 | - Use camelCase for variables and functions 45 | - Use UPPER_SNAKE_CASE for constants 46 | - File names should match the component/function they export 47 | 48 | ### Error Handling 49 | - Use try/catch blocks for async operations 50 | - Handle errors gracefully with user-friendly messages 51 | - Log errors appropriately for debugging 52 | 53 | ### Virtualization (from .cursorrules) 54 | - Use virtual scroll for lists with >100 items 55 | - Use virtualization for tables with >50 rows 56 | - Use @tanstack/react-virtual for implementation 57 | - Always pre-calculate element heights for better performance 58 | - Use memoization (React.memo, useMemo, useCallback) for list items 59 | - Optimize rendering by avoiding object creation in render functions 60 | 61 | ## Additional Rules from .cursorrules 62 | - No fantasy - don't invent data, events, sources or opinions without request 63 | - Be honest - specify what your answer is based on 64 | - Prioritize accuracy and logic over presentation 65 | - Avoid humor, metaphors, storytelling, or emotions unless specifically requested -------------------------------------------------------------------------------- /KEYS.md: -------------------------------------------------------------------------------- 1 | # Keys 2 | 3 | The purpose of the Kupi terminal was to securely trade without having to 4 | trust your keys to third-parties. 5 | 6 | We designed the terminal so that the user stores the keys on his 7 | computer or his server and does not trust them to store third-party 8 | services, including kupi.network. 9 | 10 | With kupi.network, a user can only receive public data from exchanges 11 | (orders, trades, candles) and analytical data from the kupi.network 12 | team. 13 | 14 | ## Keys types 15 | 16 | Keys consist of a pair of keys and secret_key. 17 | 18 | Keys are stored in ```private/keys.json``` in clear text. Plans to 19 | implement storage in encrypted form. 20 | 21 | | type of key | operations | 22 | | -------------- | ------------------------------------------------------ | 23 | | safe_apiKey | key for orders, trades, balances, my_orders, my_trades | 24 | | notSafe_apiKey | key for buy/sell | 25 | | danger_apiKey | key for withdraw (not realized yet) | 26 | 27 | ## Keys config structure 28 | 29 | ```js 30 | // private/keys.json 31 | [ 32 | { 33 | "id": "1654c8e6223492874293784293487293847", 34 | "name": "user@gmail.com", 35 | "parser": "ccxt", 36 | "stock": "binance", 37 | "safe_apiKey": "", 38 | "safe_secret": "", 39 | "notSafe_apiKey": "", 40 | "notSafe_secret": "", 41 | "danger_apiKey": "", 42 | "danger_secret": "", 43 | "2fa_hash": "", 44 | "2fa_secret": "", 45 | "2fa_type": "", 46 | "proxy": [], 47 | "withdrawLimit": "2", 48 | "withdrawLimitIn": "BTC", 49 | "note": "Default account, not safe key without linking to ips" 50 | } 51 | ] 52 | ``` 53 | 54 | ## Recommendations for use 55 | 56 | We recommend creating 3 separate keys on the exchange 57 | 58 | * This will increase the number of available requests to the exchange 59 | * And also reduces the number of requests over the network using unsafe and dangerous keys, because mainly secure keys will be used 60 | 61 | We strongly recommend attaching keys to your ip. 62 | 63 | If you suspect that the keys are compromised, change the keys 64 | immediately. 65 | 66 | ## Possible key storage implementations and possible security risks 67 | 68 | #### Implemented in Kupi terminal 69 | 70 | * your hard drive 71 | - Viruses can download and analyze data 72 | - Malicious libraries and packages in the code can intercept keys 73 | 74 | #### Not implemented in Kupi terminal 75 | 76 | * your hard + file encryption (will be implemented in the near future) 77 | - Viruses can download and analyze data, but will not be able to 78 | use without getting a passphrase 79 | - Viruses can intercept the passphrase 80 | - Malicious libraries and packages in the code can intercept keys 81 | * Localstorage localhost 82 | - All other sites on localhost will have access to the keys 83 | - Malicious libraries and packages in the code can intercept keys 84 | * Localstorage kupi.network or your own domain (even with ssl) 85 | - Malefactors can substitute hosts by the machine for access to 86 | keys 87 | - Malicious libraries and packages in the code can intercept keys 88 | * Localstorage + encryption 89 | - In local storage keys are stored in encrypted form, but they can 90 | be decrypted by entering the code phrase 91 | - Malicious plugins for browser can intercept keys 92 | - Viruses can intercept keys. 93 | - Malicious libraries and packages in the code can intercept keys 94 | * Different trading terminals with key storage on their servers 95 | - You trust your keys to the 3rd party 96 | - Malicious plugins for browser can intercept keys 97 | - Viruses can intercept keys 98 | -------------------------------------------------------------------------------- /REACT_VS_VUE.md: -------------------------------------------------------------------------------- 1 | # React client vs Vue client 2 | 3 | | Feature | React | Vue | 4 | |--|--|--| 5 | | Orders | + | + | 6 | | My orders | + | + | 7 | | Create order | + | + | 8 | | Trades | + | + | 9 | | My trades | + | + | 10 | | Notes widget | + | - | 11 | | Custom iframe widget | + | - | 12 | | Widget catalog | + | - | 13 | | Candles (OHLCV) | react-stockcharts | react-stockcharts, echarts | 14 | | Balance now | by stock, by all stocks, table, pie | total, table, pie | 15 | | Balance history | by stock, by all stocks, table, pie | total, table, pie | 16 | | Layout | Widgets | Fixed | 17 | | Selector stock/pair | Change in widgets by color group | Change in all widgets | 18 | | Electron app | - | Alpha version | 19 | | Accounting | - | Alpha version | 20 | | Sentiments | - | Alpha version | 21 | -------------------------------------------------------------------------------- /ROADMAP.md: -------------------------------------------------------------------------------- 1 | # Roadmap 2 | 3 | ## 0.7.0 4 | - nestjs server 5 | - refactoring: catchHead() and catchTail() 6 | - Kupi.network api authorization 7 | - Crypto signals 8 | - Trading instruments for realizing crypto signals 9 | - Accounting 10 | - Balances by stocks in % 11 | - Encrypt keys 12 | - Add/Remove keys from interface 13 | - Electron version 14 | 15 | ## 0.8.0 16 | 17 | - Kupi.network api core 18 | - Kupi.network history data 19 | - candles history data 20 | - orders history data 21 | - trades history data 22 | - Kupi.network websocket api 23 | 24 | ## 0.9.0 25 | 26 | - Global refactoring vue client 27 | - Global refactoring react client 28 | - More tests 29 | 30 | ## In future 31 | 32 | ### api 33 | 34 | - improve speed of information from stocks (now it can be in 0.2-30s, depends by stock) 35 | - more reliability 36 | 37 | ### terminal 38 | 39 | - websocket api 40 | - free arbitrage signals (one signal in one hands) 41 | - free strategies that really works 42 | - free bots 43 | - widgets for monitoring bots work 44 | - emulator trades based on realtime data 45 | - emulator trades based on history (backtesting) 46 | - i18n language support 47 | - analytics widgets 48 | - more stocks 49 | - app version based on electron 50 | - online version 51 | - improve security 52 | - auditing 53 | - tests 54 | 55 | ### dex 56 | 57 | - franchise 58 | - ethereum 59 | - options/features/financial_instruments 60 | - shorts 61 | - qtum 62 | - options/features/financial_instruments 63 | - shorts 64 | - sidechain 65 | - options/features/financial_instruments 66 | - shorts 67 | - another blockchains 68 | - tests 69 | -------------------------------------------------------------------------------- /VOCABULARY.md: -------------------------------------------------------------------------------- 1 | ## Vocabulary 2 | 3 | ### Base 4 | 5 | **task** – this is the task for the script to track the trading position (emulated wrapper over the order) 6 | order is an exposed / realized / partially_realized position on the stock 7 | 8 | **orders_queue** – queue for order execution (to work with rate limit) 9 | 10 | **stock** – current stock stock for trading (alt. in ccxt – stock) 11 | 12 | **market** – global_market / stock, which we are looking at, in order to base prices on which it is possible to sell the desired coin with 100% confidence (it could be a coinmarketcap, or maybe a binance, or maybe some other stock) 13 | 14 | **pair** – a pair (alt. in ccxt: symbol), example DNT_BTC (in ccxt: DNT/BTC) 15 | 16 | **coin** - crypto coin or another trading instrument 17 | 18 | **coin_from** – the left part of the pair, small coin (in ccxt: base) 19 | 20 | **coin_to** – the right side of the pair, a large coin (in ccxt: quote) 21 | 22 | **stock_from** – on which stock we buy 23 | 24 | **stock_to** – on which stock we sell 25 | 26 | ### Keys 27 | 28 | **public_key** – the public part of the key for working with the stock (in config: apiKey) 29 | 30 | **private_key** – the private part of the key to work with the stock (in config: secret) 31 | 32 | **safe_apiKey** – key only for orders, trades, balances, my_orders, my_trades 33 | 34 | **notSafe_apiKey** – key only for buy / sell 35 | 36 | **danger_apiKey** – key only for withdraw (not realized yet) 37 | 38 | ### Kupi 39 | 40 | **kupi_server** – is an external service, not open source, parses public data from a multitude of exchanges and gives away free of charge for api: trades, orders, candles 41 | 42 | **kupi_backend** – kupi_server backend 43 | 44 | **kupi_api** – api kupi_server 45 | 46 | ### CCXT 47 | 48 | **ccxt** – aggregator library, to work with a large number of exchanges in the same style 49 | 50 | **ccxt_api** – api to work with ccxt, that realized in ```client_backend``` 51 | 52 | ### Client 53 | 54 | **client_server** – the hardware on which ``client_backend``` runs 55 | 56 | **client_backend** – express js server that is responsible for authorization, working with keys, working with ccxt_api and kupi_api 57 | 58 | **client_api** – api raised in ```client_backend``` 59 | 60 | **client_frontend** – interface on react/vue to work with ```client_backend``` via ```client_api``` 61 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suenot/profitmaker/10a474032fdacdda5af794f15d092180c9740c20/bun.lockb -------------------------------------------------------------------------------- /components.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://ui.shadcn.com/schema.json", 3 | "style": "default", 4 | "rsc": false, 5 | "tsx": true, 6 | "tailwind": { 7 | "config": "tailwind.config.ts", 8 | "css": "src/index.css", 9 | "baseColor": "slate", 10 | "cssVariables": true, 11 | "prefix": "" 12 | }, 13 | "aliases": { 14 | "components": "@/components", 15 | "utils": "@/lib/utils", 16 | "ui": "@/components/ui", 17 | "lib": "@/lib", 18 | "hooks": "@/hooks" 19 | } 20 | } -------------------------------------------------------------------------------- /debug_orders.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Orders Debug 7 | 43 | 44 | 45 |

Orders Debug Console

46 |

Open the browser console to see detailed logs from the UserOrdersTab component.

47 | 48 |
49 | 50 | 51 | 52 |
53 | 54 |
55 | 56 | 109 | 110 | -------------------------------------------------------------------------------- /demo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suenot/profitmaker/10a474032fdacdda5af794f15d092180c9740c20/demo.png -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | import js from "@eslint/js"; 2 | import globals from "globals"; 3 | import reactHooks from "eslint-plugin-react-hooks"; 4 | import reactRefresh from "eslint-plugin-react-refresh"; 5 | import tseslint from "typescript-eslint"; 6 | 7 | export default tseslint.config( 8 | { ignores: ["dist"] }, 9 | { 10 | extends: [js.configs.recommended, ...tseslint.configs.recommended], 11 | files: ["**/*.{ts,tsx}"], 12 | languageOptions: { 13 | ecmaVersion: 2020, 14 | globals: globals.browser, 15 | }, 16 | plugins: { 17 | "react-hooks": reactHooks, 18 | "react-refresh": reactRefresh, 19 | }, 20 | rules: { 21 | ...reactHooks.configs.recommended.rules, 22 | "react-refresh/only-export-components": [ 23 | "warn", 24 | { allowConstantExport: true }, 25 | ], 26 | "@typescript-eslint/no-unused-vars": "off", 27 | }, 28 | } 29 | ); 30 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Profitmaker 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vite_react_shadcn_ts", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "build:dev": "vite build --mode development", 10 | "lint": "eslint .", 11 | "preview": "vite preview", 12 | "server": "tsx --tsconfig tsconfig.server.json express.ts", 13 | "server:dev": "tsx watch --tsconfig tsconfig.server.json express.ts", 14 | "test": "vitest", 15 | "test:ui": "vitest --ui" 16 | }, 17 | "dependencies": { 18 | "@hookform/resolvers": "^3.9.0", 19 | "@radix-ui/react-accordion": "^1.2.0", 20 | "@radix-ui/react-alert-dialog": "^1.1.1", 21 | "@radix-ui/react-aspect-ratio": "^1.1.0", 22 | "@radix-ui/react-avatar": "^1.1.0", 23 | "@radix-ui/react-checkbox": "^1.1.1", 24 | "@radix-ui/react-collapsible": "^1.1.0", 25 | "@radix-ui/react-context-menu": "^2.2.1", 26 | "@radix-ui/react-dialog": "^1.1.2", 27 | "@radix-ui/react-dropdown-menu": "^2.1.1", 28 | "@radix-ui/react-hover-card": "^1.1.1", 29 | "@radix-ui/react-label": "^2.1.0", 30 | "@radix-ui/react-menubar": "^1.1.1", 31 | "@radix-ui/react-navigation-menu": "^1.2.0", 32 | "@radix-ui/react-popover": "^1.1.1", 33 | "@radix-ui/react-progress": "^1.1.0", 34 | "@radix-ui/react-radio-group": "^1.2.0", 35 | "@radix-ui/react-scroll-area": "^1.1.0", 36 | "@radix-ui/react-select": "^2.1.1", 37 | "@radix-ui/react-separator": "^1.1.0", 38 | "@radix-ui/react-slider": "^1.2.0", 39 | "@radix-ui/react-slot": "^1.1.0", 40 | "@radix-ui/react-switch": "^1.1.0", 41 | "@radix-ui/react-tabs": "^1.1.0", 42 | "@radix-ui/react-toast": "^1.2.1", 43 | "@radix-ui/react-toggle": "^1.1.0", 44 | "@radix-ui/react-toggle-group": "^1.1.0", 45 | "@radix-ui/react-tooltip": "^1.1.4", 46 | "@tanstack/react-query": "^5.56.2", 47 | "@tanstack/react-router": "^1.120.13", 48 | "@tanstack/react-virtual": "^3.13.10", 49 | "@tanstack/router-devtools": "^1.120.13", 50 | "@types/socket.io": "^3.0.1", 51 | "ccxt": "^4.4.93", 52 | "class-variance-authority": "^0.7.1", 53 | "clsx": "^2.1.1", 54 | "cmdk": "^1.0.0", 55 | "cors": "^2.8.5", 56 | "date-fns": "^3.6.0", 57 | "embla-carousel-react": "^8.3.0", 58 | "express": "^5.1.0", 59 | "immer": "^10.1.1", 60 | "input-otp": "^1.2.4", 61 | "lucide-react": "^0.462.0", 62 | "next-themes": "^0.3.0", 63 | "night-vision": "^0.4.0", 64 | "react": "^18.3.1", 65 | "react-day-picker": "^8.10.1", 66 | "react-dom": "^18.3.1", 67 | "react-hook-form": "^7.53.0", 68 | "react-resizable-panels": "^2.1.3", 69 | "react-router-dom": "^6.26.2", 70 | "recharts": "^2.12.7", 71 | "socket.io": "^4.8.1", 72 | "socket.io-client": "^4.8.1", 73 | "sonner": "^1.5.0", 74 | "tailwind-merge": "^2.5.2", 75 | "tailwindcss-animate": "^1.0.7", 76 | "uuid": "^11.1.0", 77 | "vaul": "^0.9.3", 78 | "zod": "^3.23.8", 79 | "zustand": "^5.0.5" 80 | }, 81 | "devDependencies": { 82 | "@eslint/js": "^9.9.0", 83 | "@tailwindcss/typography": "^0.5.15", 84 | "@types/cors": "^2.8.19", 85 | "@types/express": "^5.0.3", 86 | "@types/node": "^22.5.5", 87 | "@types/react": "^18.3.3", 88 | "@types/react-dom": "^18.3.0", 89 | "@types/uuid": "^10.0.0", 90 | "@vitejs/plugin-react-swc": "^3.5.0", 91 | "@vitest/ui": "^3.2.4", 92 | "autoprefixer": "^10.4.20", 93 | "eslint": "^9.9.0", 94 | "eslint-plugin-react-hooks": "^5.1.0-rc.0", 95 | "eslint-plugin-react-refresh": "^0.4.9", 96 | "globals": "^15.9.0", 97 | "jsdom": "^26.1.0", 98 | "lovable-tagger": "^1.1.7", 99 | "nodemon": "^3.1.10", 100 | "postcss": "^8.4.47", 101 | "tailwindcss": "^3.4.11", 102 | "tsx": "^4.20.3", 103 | "typescript": "^5.5.3", 104 | "typescript-eslint": "^8.0.1", 105 | "vite": "^5.4.1", 106 | "vitest": "^3.2.4" 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suenot/profitmaker/10a474032fdacdda5af794f15d092180c9740c20/public/apple-touch-icon.png -------------------------------------------------------------------------------- /public/favicon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suenot/profitmaker/10a474032fdacdda5af794f15d092180c9740c20/public/favicon-96x96.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suenot/profitmaker/10a474032fdacdda5af794f15d092180c9740c20/public/favicon.ico -------------------------------------------------------------------------------- /public/og-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suenot/profitmaker/10a474032fdacdda5af794f15d092180c9740c20/public/og-image.png -------------------------------------------------------------------------------- /public/placeholder.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Profitmaker open source tradingterminal", 3 | "short_name": "Profitmaker", 4 | "icons": [ 5 | { 6 | "src": "/web-app-manifest-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png", 9 | "purpose": "maskable" 10 | }, 11 | { 12 | "src": "/web-app-manifest-512x512.png", 13 | "sizes": "512x512", 14 | "type": "image/png", 15 | "purpose": "maskable" 16 | } 17 | ], 18 | "theme_color": "#ffffff", 19 | "background_color": "#ffffff", 20 | "display": "standalone" 21 | } -------------------------------------------------------------------------------- /public/web-app-manifest-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suenot/profitmaker/10a474032fdacdda5af794f15d092180c9740c20/public/web-app-manifest-192x192.png -------------------------------------------------------------------------------- /public/web-app-manifest-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/suenot/profitmaker/10a474032fdacdda5af794f15d092180c9740c20/public/web-app-manifest-512x512.png -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import { Toaster } from "@/components/ui/toaster"; 2 | import { Toaster as Sonner } from "@/components/ui/sonner"; 3 | import { TooltipProvider } from "@/components/ui/tooltip"; 4 | import { CookieNotification } from "@/components/ui/cookie-notification"; 5 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 6 | import { BrowserRouter, Routes, Route } from "react-router-dom"; 7 | import { ThemeProvider } from "@/hooks/useTheme"; 8 | import Index from "./pages/Index"; 9 | import NotFound from "./pages/NotFound"; 10 | import BottomLeftInfo from './components/BottomLeftInfo'; 11 | import RightClickInfo from './components/RightClickInfo'; 12 | import TestProviderIntegration from './components/TestProviderIntegration'; 13 | import TestTimeframes from './components/TestTimeframes'; 14 | import { TestChartWidget } from './components/TestChartWidget'; 15 | import TestCCXTServerProvider from './components/TestCCXTServerProvider'; 16 | import TestDebugWidgetCCXTServer from './components/TestDebugWidgetCCXTServer'; 17 | import WidgetSettingsManager from './components/WidgetSettingsManager'; 18 | 19 | const queryClient = new QueryClient(); 20 | 21 | const App = () => ( 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | } /> 30 | } /> 31 | } /> 32 | } /> 33 | } /> 34 | } /> 35 | {/* ADD ALL CUSTOM ROUTES ABOVE THE CATCH-ALL "*" ROUTE */} 36 | } /> 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | ); 47 | 48 | export default App; 49 | -------------------------------------------------------------------------------- /src/components/AlignmentGuides.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { GuideLineType } from '@/types/alignmentGuides'; 4 | 5 | interface AlignmentGuidesProps { 6 | guideLines: GuideLineType[]; 7 | } 8 | 9 | const AlignmentGuides: React.FC = ({ guideLines }) => { 10 | return ( 11 | <> 12 | {guideLines.map((guideLine, index) => ( 13 |
26 | ))} 27 | 28 | ); 29 | }; 30 | 31 | export default AlignmentGuides; 32 | -------------------------------------------------------------------------------- /src/components/BottomLeftInfo.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | const BottomLeftInfo: React.FC = () => { 4 | return ( 5 |
6 | 2 059,62 USD 7 | | 8 | 9 | Today -1,24 USD (0,06%) 10 | 11 |
12 | ); 13 | }; 14 | 15 | export default BottomLeftInfo; -------------------------------------------------------------------------------- /src/components/CollapsedWidgetsZone.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { cn } from '@/lib/utils'; 3 | import { useDashboardStore } from '@/store/dashboardStore'; 4 | 5 | interface CollapsedWidgetsZoneProps { 6 | className?: string; 7 | } 8 | 9 | const COLLAPSED_WIDGET_HEIGHT = 40; 10 | const COLLAPSED_WIDGET_WIDTH = 200; 11 | 12 | export const CollapsedWidgetsZone: React.FC = ({ className }) => { 13 | const activeDashboardId = useDashboardStore(s => s.activeDashboardId); 14 | const dashboards = useDashboardStore(s => s.dashboards); 15 | const toggleWidgetMinimized = useDashboardStore(s => s.toggleWidgetMinimized); 16 | 17 | const activeDashboard = dashboards.find(d => d.id === activeDashboardId); 18 | const collapsedWidgets = activeDashboard?.widgets.filter(w => w.isMinimized) || []; 19 | 20 | const handleWidgetClick = (widgetId: string) => { 21 | if (activeDashboardId) { 22 | toggleWidgetMinimized(activeDashboardId, widgetId); 23 | } 24 | }; 25 | 26 | if (collapsedWidgets.length === 0) { 27 | return null; 28 | } 29 | 30 | return ( 31 |
39 | {collapsedWidgets.map((widget, index) => ( 40 | 60 | ))} 61 |
62 | ); 63 | }; 64 | 65 | export default CollapsedWidgetsZone; -------------------------------------------------------------------------------- /src/components/DebugUserData.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useUserStore } from '../store/userStore'; 3 | 4 | export const DebugUserData: React.FC = () => { 5 | const { users, activeUserId } = useUserStore(); 6 | const activeUser = users.find(u => u.id === activeUserId); 7 | 8 | return ( 9 |
10 |
11 |

Active User ID:

12 |

{activeUserId || 'None'}

13 |
14 | 15 |
16 |

Total Users:

17 |

{users.length}

18 |
19 | 20 | {activeUser && ( 21 |
22 |

Active User:

23 |
24 |

Email: {activeUser.email}

25 |

Name: {activeUser.name || 'Not set'}

26 |

Accounts: {activeUser.accounts.length}

27 | 28 | {activeUser.accounts.length > 0 && ( 29 |
30 |
Accounts:
31 |
32 | {activeUser.accounts.map((account, index) => ( 33 |
34 |

#{index + 1}

35 |

Exchange: {account.exchange}

36 |

Email: {account.email}

37 |

Has API Key: {account.key ? 'Yes' : 'No'}

38 |

Has Secret: {account.privateKey ? 'Yes' : 'No'}

39 |
40 | ))} 41 |
42 |
43 | )} 44 |
45 |
46 | )} 47 | 48 | {!activeUser && ( 49 |
50 |

No active user selected

51 |
52 | )} 53 |
54 | ); 55 | }; -------------------------------------------------------------------------------- /src/components/RightClickInfo.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { useRightClickInfo } from '@/hooks/useRightClickInfo'; 3 | 4 | const RightClickInfo: React.FC = () => { 5 | const { showMessage, dismissMessage } = useRightClickInfo(); 6 | 7 | useEffect(() => { 8 | if (!showMessage) return; 9 | 10 | const handleContextMenu = () => { 11 | dismissMessage(); 12 | }; 13 | 14 | // Add event listener for right-click 15 | document.addEventListener('contextmenu', handleContextMenu); 16 | 17 | return () => { 18 | document.removeEventListener('contextmenu', handleContextMenu); 19 | }; 20 | }, [showMessage, dismissMessage]); 21 | 22 | if (!showMessage) return null; 23 | 24 | return ( 25 |
26 | Right-click to add widgets 27 |
28 | ); 29 | }; 30 | 31 | export default RightClickInfo; -------------------------------------------------------------------------------- /src/components/SettingsDrawer.tsx: -------------------------------------------------------------------------------- 1 | import React, { ReactNode } from 'react'; 2 | import { createPortal } from 'react-dom'; 3 | import { X } from 'lucide-react'; 4 | import { cn } from '@/lib/utils'; 5 | 6 | interface SettingsDrawerProps { 7 | isOpen: boolean; 8 | onClose: () => void; 9 | title: string; 10 | children: ReactNode; 11 | } 12 | 13 | const SettingsDrawer: React.FC = ({ 14 | isOpen, 15 | onClose, 16 | title, 17 | children 18 | }) => { 19 | if (!isOpen) return null; 20 | 21 | return createPortal( 22 |
23 | {/* Backdrop */} 24 |
28 | 29 | {/* Drawer */} 30 |
35 | {/* Header */} 36 |
37 |

{title}

38 | 44 |
45 | 46 | {/* Content */} 47 |
48 | {children} 49 |
50 |
51 |
, 52 | document.body 53 | ); 54 | }; 55 | 56 | export default SettingsDrawer; -------------------------------------------------------------------------------- /src/components/TestTimeframes.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from 'react'; 2 | import { useDataProviderStore } from '../store/dataProviderStore'; 3 | import { getCCXT } from '../store/utils/ccxtUtils'; 4 | 5 | const TestTimeframes: React.FC = () => { 6 | const { getTimeframesForExchange } = useDataProviderStore(); 7 | const [results, setResults] = useState>({}); 8 | 9 | useEffect(() => { 10 | const testExchanges = ['binance', 'bybit', 'okx', 'kucoin']; 11 | const testResults: Record = {}; 12 | 13 | // Test direct CCXT access 14 | console.log('🧪 [TestTimeframes] Testing direct CCXT access'); 15 | const ccxt = getCCXT(); 16 | if (ccxt) { 17 | console.log('✅ [TestTimeframes] CCXT is available'); 18 | console.log('📋 [TestTimeframes] Available exchanges:', Object.keys(ccxt).filter(key => typeof ccxt[key] === 'function').slice(0, 10)); 19 | 20 | // Test specific exchange 21 | if (ccxt.binance) { 22 | try { 23 | const binanceInstance = new ccxt.binance(); 24 | console.log('📊 [TestTimeframes] Binance timeframes:', binanceInstance.timeframes); 25 | testResults.binance_direct = binanceInstance.timeframes; 26 | } catch (error) { 27 | console.error('❌ [TestTimeframes] Error creating Binance instance:', error); 28 | testResults.binance_direct = { error: error.message }; 29 | } 30 | } 31 | } else { 32 | console.error('❌ [TestTimeframes] CCXT is not available'); 33 | testResults.ccxt_available = false; 34 | } 35 | 36 | // Test our store method 37 | testExchanges.forEach(exchange => { 38 | console.log(`🧪 [TestTimeframes] Testing ${exchange}`); 39 | try { 40 | const timeframes = getTimeframesForExchange(exchange); 41 | testResults[exchange] = timeframes; 42 | console.log(`✅ [TestTimeframes] ${exchange} timeframes:`, timeframes); 43 | } catch (error) { 44 | console.error(`❌ [TestTimeframes] Error getting timeframes for ${exchange}:`, error); 45 | testResults[exchange] = { error: error.message }; 46 | } 47 | }); 48 | 49 | setResults(testResults); 50 | }, [getTimeframesForExchange]); 51 | 52 | return ( 53 |
54 |

🧪 Timeframes Test Results

55 |
56 |         {JSON.stringify(results, null, 2)}
57 |       
58 |
59 | ); 60 | }; 61 | 62 | export default TestTimeframes; -------------------------------------------------------------------------------- /src/components/WidgetSettingsManager.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import SettingsDrawer from './SettingsDrawer'; 3 | import { useSettingsDrawerStore } from '@/store/settingsDrawerStore'; 4 | import ChartSettingsWrapper from './widgets/ChartSettingsWrapper'; 5 | import TradesSettingsWrapper from './widgets/TradesSettingsWrapper'; 6 | import OrderBookSettingsWrapper from './widgets/OrderBookSettingsWrapper'; 7 | import UserBalancesSettingsWrapper from './widgets/UserBalancesSettingsWrapper'; 8 | import UserTradingDataSettingsWrapper from './widgets/UserTradingDataSettingsWrapper'; 9 | 10 | const WidgetSettingsManager: React.FC = () => { 11 | const { isOpen, widgetId, widgetType, widgetTitle, groupId, closeDrawer } = useSettingsDrawerStore(); 12 | 13 | const renderSettings = () => { 14 | if (!widgetId || !widgetType) return null; 15 | 16 | switch (widgetType) { 17 | case 'chart': 18 | return ; 19 | case 'orderbook': 20 | case 'orderBook': 21 | return ; 22 | case 'portfolio': 23 | return
Portfolio settings coming soon...
; 24 | case 'trades': 25 | return ; 26 | case 'orderForm': 27 | return
Order Form settings coming soon...
; 28 | case 'userBalances': 29 | return ; 30 | case 'userTradingData': 31 | return ; 32 | default: 33 | return
No settings available for this widget
; 34 | } 35 | }; 36 | 37 | return ( 38 | 43 | {renderSettings()} 44 | 45 | ); 46 | }; 47 | 48 | export default WidgetSettingsManager; -------------------------------------------------------------------------------- /src/components/ui/ErrorBoundary.tsx: -------------------------------------------------------------------------------- 1 | import React, { Component, ErrorInfo, ReactNode } from 'react'; 2 | import { AlertTriangle, RefreshCw } from 'lucide-react'; 3 | import { Card, CardContent, CardHeader, CardTitle } from './card'; 4 | import { Button } from './button'; 5 | 6 | interface Props { 7 | children: ReactNode; 8 | fallbackTitle?: string; 9 | showDetails?: boolean; 10 | } 11 | 12 | interface State { 13 | hasError: boolean; 14 | error?: Error; 15 | errorInfo?: ErrorInfo; 16 | } 17 | 18 | export class ErrorBoundary extends Component { 19 | public state: State = { 20 | hasError: false 21 | }; 22 | 23 | public static getDerivedStateFromError(error: Error): State { 24 | // Обновляем состояние, чтобы следующий рендер показал fallback UI 25 | return { hasError: true, error }; 26 | } 27 | 28 | public componentDidCatch(error: Error, errorInfo: ErrorInfo) { 29 | console.error('🛡️ Error Boundary caught an error:', error, errorInfo); 30 | 31 | this.setState({ 32 | error, 33 | errorInfo 34 | }); 35 | 36 | // Here you can send error to monitoring system 37 | // e.g. Sentry, LogRocket, etc. 38 | } 39 | 40 | private handleRetry = () => { 41 | this.setState({ hasError: false, error: undefined, errorInfo: undefined }); 42 | }; 43 | 44 | public render() { 45 | if (this.state.hasError) { 46 | const { fallbackTitle = "Что-то пошло не так", showDetails = false } = this.props; 47 | 48 | return ( 49 | 50 | 51 | 52 | 53 | {fallbackTitle} 54 | 55 | 56 | 57 |

58 | Приложение столкнулось с неожиданной ошибкой, но мы перехватили её и предотвратили крах. 59 |

60 | 61 | {showDetails && this.state.error && ( 62 |
63 | Детали ошибки: 64 |
65 |                   {this.state.error.message}
66 |                 
67 |
68 | )} 69 | 70 |
71 | 75 | 76 | 83 |
84 |
85 |
86 | ); 87 | } 88 | 89 | return this.props.children; 90 | } 91 | } -------------------------------------------------------------------------------- /src/components/ui/SelectorDemo.tsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import GroupColorSelector from './GroupColorSelector'; 3 | import InstrumentSelector from './InstrumentSelector'; 4 | 5 | const SelectorDemo: React.FC = () => { 6 | const [selectedGroupId, setSelectedGroupId] = useState(undefined); 7 | 8 | return ( 9 |
10 |

New Selector Components Demo

11 | 12 |
13 | {/* Separate components demo */} 14 |
15 |

Separated Components

16 |
17 |
18 | 19 | 23 |
24 |
25 | 26 | 31 |
32 |
33 |
34 | 35 | {/* Widget header simulation */} 36 |
37 |

In Widget Header (как в WidgetSimple)

38 |
39 |
40 | 45 | 50 |
51 | Widget Title 52 |
53 |
54 | Settings • Minimize • Close 55 |
56 |
57 |
58 |
59 | 60 | {/* State display */} 61 |
62 |

Current State

63 |
64 |
65 |
Selected Group ID: {selectedGroupId || 'None'}
66 |
67 |
68 |
69 | 70 | {/* Benefits */} 71 |
72 |

Benefits of Separation

73 |
74 |
Clear UX: Color selection and instrument search are separate concerns
75 |
Better Layout: More flexible positioning and sizing
76 |
Simpler Logic: Each component handles one responsibility
77 |
Reusable: Components can be used independently
78 |
Inspired by Design: Matches the clean separation shown in reference app
79 |
80 |
81 |
82 |
83 | ); 84 | }; 85 | 86 | export default SelectorDemo; -------------------------------------------------------------------------------- /src/components/ui/TimeframeSelect.tsx: -------------------------------------------------------------------------------- 1 | import React, { useMemo } from 'react'; 2 | import { Clock } from 'lucide-react'; 3 | import { Timeframe } from '../../types/dataProviders'; 4 | import { useDataProviderStore } from '../../store/dataProviderStore'; 5 | 6 | // Mapping timeframes to display labels (CCXT format - lowercase) 7 | const TIMEFRAME_LABELS: Record = { 8 | '1m': '1m', 9 | '3m': '3m', 10 | '5m': '5m', 11 | '15m': '15m', 12 | '30m': '30m', 13 | '1h': '1h', 14 | '2h': '2h', 15 | '4h': '4h', 16 | '6h': '6h', 17 | '12h': '12h', 18 | '1d': '1d', 19 | '1w': '1w', 20 | '1M': '1M', 21 | }; 22 | 23 | interface TimeframeSelectProps { 24 | value: Timeframe; 25 | onChange: (timeframe: Timeframe) => void; 26 | exchange: string; // NEW: Required exchange parameter 27 | className?: string; 28 | } 29 | 30 | const TimeframeSelect: React.FC = ({ 31 | value, 32 | onChange, 33 | exchange, 34 | className = '' 35 | }) => { 36 | const { getTimeframesForExchange } = useDataProviderStore(); 37 | 38 | // Get available timeframes for this exchange 39 | const availableTimeframes = useMemo(() => { 40 | console.log(`🎯 [TimeframeSelect] Getting timeframes for exchange: ${exchange}`); 41 | const timeframes = getTimeframesForExchange(exchange); 42 | console.log(`🎯 [TimeframeSelect] Received timeframes:`, timeframes); 43 | return timeframes.map(tf => ({ 44 | id: tf, 45 | label: TIMEFRAME_LABELS[tf] || tf.toUpperCase() 46 | })); 47 | }, [exchange, getTimeframesForExchange]); 48 | 49 | // Ensure current value is available, fallback to first available if not 50 | const currentValue = useMemo(() => { 51 | const isCurrentValueAvailable = availableTimeframes.some(tf => tf.id === value); 52 | if (!isCurrentValueAvailable && availableTimeframes.length > 0) { 53 | // Auto-switch to first available timeframe 54 | const firstAvailable = availableTimeframes[0].id; 55 | setTimeout(() => onChange(firstAvailable), 0); 56 | return firstAvailable; 57 | } 58 | return value; 59 | }, [value, availableTimeframes, onChange]); 60 | 61 | return ( 62 |
65 | 66 | 82 |
83 | ); 84 | }; 85 | 86 | export default TimeframeSelect; -------------------------------------------------------------------------------- /src/components/ui/accordion.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as AccordionPrimitive from "@radix-ui/react-accordion" 3 | import { ChevronDown } from "lucide-react" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const Accordion = AccordionPrimitive.Root 8 | 9 | const AccordionItem = React.forwardRef< 10 | React.ElementRef, 11 | React.ComponentPropsWithoutRef 12 | >(({ className, ...props }, ref) => ( 13 | 18 | )) 19 | AccordionItem.displayName = "AccordionItem" 20 | 21 | const AccordionTrigger = React.forwardRef< 22 | React.ElementRef, 23 | React.ComponentPropsWithoutRef 24 | >(({ className, children, ...props }, ref) => ( 25 | 26 | svg]:rotate-180", 30 | className 31 | )} 32 | {...props} 33 | > 34 | {children} 35 | 36 | 37 | 38 | )) 39 | AccordionTrigger.displayName = AccordionPrimitive.Trigger.displayName 40 | 41 | const AccordionContent = React.forwardRef< 42 | React.ElementRef, 43 | React.ComponentPropsWithoutRef 44 | >(({ className, children, ...props }, ref) => ( 45 | 50 |
{children}
51 |
52 | )) 53 | 54 | AccordionContent.displayName = AccordionPrimitive.Content.displayName 55 | 56 | export { Accordion, AccordionItem, AccordionTrigger, AccordionContent } 57 | -------------------------------------------------------------------------------- /src/components/ui/alert.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const alertVariants = cva( 7 | "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", 8 | { 9 | variants: { 10 | variant: { 11 | default: "bg-background text-foreground", 12 | destructive: 13 | "border-destructive/50 text-destructive dark:border-destructive [&>svg]:text-destructive", 14 | }, 15 | }, 16 | defaultVariants: { 17 | variant: "default", 18 | }, 19 | } 20 | ) 21 | 22 | const Alert = React.forwardRef< 23 | HTMLDivElement, 24 | React.HTMLAttributes & VariantProps 25 | >(({ className, variant, ...props }, ref) => ( 26 |
32 | )) 33 | Alert.displayName = "Alert" 34 | 35 | const AlertTitle = React.forwardRef< 36 | HTMLParagraphElement, 37 | React.HTMLAttributes 38 | >(({ className, ...props }, ref) => ( 39 |
44 | )) 45 | AlertTitle.displayName = "AlertTitle" 46 | 47 | const AlertDescription = React.forwardRef< 48 | HTMLParagraphElement, 49 | React.HTMLAttributes 50 | >(({ className, ...props }, ref) => ( 51 |
56 | )) 57 | AlertDescription.displayName = "AlertDescription" 58 | 59 | export { Alert, AlertTitle, AlertDescription } 60 | -------------------------------------------------------------------------------- /src/components/ui/aspect-ratio.tsx: -------------------------------------------------------------------------------- 1 | import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio" 2 | 3 | const AspectRatio = AspectRatioPrimitive.Root 4 | 5 | export { AspectRatio } 6 | -------------------------------------------------------------------------------- /src/components/ui/avatar.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import * as AvatarPrimitive from "@radix-ui/react-avatar" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const Avatar = React.forwardRef< 7 | React.ElementRef, 8 | React.ComponentPropsWithoutRef 9 | >(({ className, ...props }, ref) => ( 10 | 18 | )) 19 | Avatar.displayName = AvatarPrimitive.Root.displayName 20 | 21 | const AvatarImage = React.forwardRef< 22 | React.ElementRef, 23 | React.ComponentPropsWithoutRef 24 | >(({ className, ...props }, ref) => ( 25 | 30 | )) 31 | AvatarImage.displayName = AvatarPrimitive.Image.displayName 32 | 33 | const AvatarFallback = React.forwardRef< 34 | React.ElementRef, 35 | React.ComponentPropsWithoutRef 36 | >(({ className, ...props }, ref) => ( 37 | 45 | )) 46 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName 47 | 48 | export { Avatar, AvatarImage, AvatarFallback } 49 | -------------------------------------------------------------------------------- /src/components/ui/badge.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { cva, type VariantProps } from "class-variance-authority" 3 | 4 | import { cn } from "@/lib/utils" 5 | 6 | const badgeVariants = cva( 7 | "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", 8 | { 9 | variants: { 10 | variant: { 11 | default: 12 | "border-transparent bg-primary text-primary-foreground hover:bg-primary/80", 13 | secondary: 14 | "border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80", 15 | destructive: 16 | "border-transparent bg-destructive text-destructive-foreground hover:bg-destructive/80", 17 | outline: "text-foreground", 18 | }, 19 | }, 20 | defaultVariants: { 21 | variant: "default", 22 | }, 23 | } 24 | ) 25 | 26 | export interface BadgeProps 27 | extends React.HTMLAttributes, 28 | VariantProps {} 29 | 30 | function Badge({ className, variant, ...props }: BadgeProps) { 31 | return ( 32 |
33 | ) 34 | } 35 | 36 | export { Badge, badgeVariants } 37 | -------------------------------------------------------------------------------- /src/components/ui/breadcrumb.tsx: -------------------------------------------------------------------------------- 1 | import * as React from "react" 2 | import { Slot } from "@radix-ui/react-slot" 3 | import { ChevronRight, MoreHorizontal } from "lucide-react" 4 | 5 | import { cn } from "@/lib/utils" 6 | 7 | const Breadcrumb = React.forwardRef< 8 | HTMLElement, 9 | React.ComponentPropsWithoutRef<"nav"> & { 10 | separator?: React.ReactNode 11 | } 12 | >(({ ...props }, ref) =>