├── .gitignore ├── README.md ├── bun.lockb ├── components.json ├── eslint.config.js ├── index.html ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── favicon.ico ├── lovable-uploads │ ├── 0e3b9242-069b-4a19-b5ad-8f96b69d7d54.png │ ├── 19d0bac1-2f20-4dcb-8a71-c65c4635deb8.png │ ├── 2e7bc354-d939-480c-b0dc-7aa03dbde994.png │ └── 56808916-d8ae-4d5a-8aa0-f0d671bc7717.png ├── mockServiceWorker.js ├── placeholder.svg └── robots.txt ├── src ├── App.css ├── App.tsx ├── ClientProfile.tsx ├── components │ ├── CustomErrorBoundary.tsx │ ├── ErrorBoundary.tsx │ ├── auth │ │ ├── LoginForm.tsx │ │ ├── ProtectedRoute.tsx │ │ └── callback.tsx │ ├── calendar │ │ ├── BookingLink.tsx │ │ ├── BookingView.tsx │ │ ├── CalendarContacts.tsx │ │ ├── CalendarIntegration.tsx │ │ ├── CalendarIntegrationDialog.tsx │ │ ├── CalendlyBookingSystem.tsx │ │ ├── EditBookingTypeDialog.tsx │ │ └── MakeCalendarIntegration.tsx │ ├── chatbot │ │ ├── ChatDrawer.tsx │ │ ├── ChatbotUI.tsx │ │ └── KnowledgeEditor.tsx │ ├── clients │ │ └── ClientsTable.tsx │ ├── compatibility │ │ └── BrowserCompatibilityCheck.tsx │ ├── content │ │ ├── ContentCreationForm.tsx │ │ ├── ContentEditDialog.tsx │ │ ├── ContentList.tsx │ │ ├── ContentScheduling.tsx │ │ └── SocialMediaConnect.tsx │ ├── dashboard │ │ ├── ActivityFeed.tsx │ │ ├── DashboardStats.tsx │ │ ├── DealsOverview.tsx │ │ ├── StatCard.tsx │ │ └── TasksPanel.tsx │ ├── deals │ │ ├── CustomFieldsManager.tsx │ │ ├── DealDetailDialog.tsx │ │ ├── DealForm.tsx │ │ ├── DealFormFields.tsx │ │ ├── EditDealDialog.tsx │ │ └── types.ts │ ├── deployment │ │ └── DeploymentChecklist.tsx │ ├── email │ │ └── MailchimpConnect.tsx │ ├── integrations │ │ ├── CustomWebhookConnect.tsx │ │ ├── GoogleCalendarConnect.tsx │ │ ├── MakeConnect.tsx │ │ ├── YextConnect.tsx │ │ └── ZapierConnect.tsx │ ├── layout │ │ ├── MobileSidebar.tsx │ │ ├── Navbar.tsx │ │ └── Sidebar.tsx │ ├── master-account │ │ ├── AddClientForm.tsx │ │ ├── ClientDirectory.tsx │ │ ├── ClientPerformanceTable.tsx │ │ ├── ClientSalesChart.tsx │ │ ├── ClientSwitcher.tsx │ │ └── SalesSummaryCards.tsx │ ├── notifications │ │ └── NotificationsPopover.tsx │ ├── pipeline │ │ └── StageManager.tsx │ ├── settings │ │ └── TeamMembers.tsx │ ├── theme │ │ ├── ThemeProvider.tsx │ │ └── ThemeToggle.tsx │ ├── ui │ │ ├── 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 │ │ ├── column-customizer.tsx │ │ ├── command.tsx │ │ ├── context-menu.tsx │ │ ├── dialog.tsx │ │ ├── drawer.tsx │ │ ├── dropdown-menu.tsx │ │ ├── error-fallback.tsx │ │ ├── form.tsx │ │ ├── hover-card.tsx │ │ ├── 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 │ └── website │ │ ├── AddPageDialog.tsx │ │ ├── AllPagesTab.tsx │ │ ├── DevicesTab.tsx │ │ ├── EditPageDialog.tsx │ │ ├── InsightsTab.tsx │ │ ├── LandingPagesTab.tsx │ │ ├── RealTimeAnalytics.tsx │ │ ├── RealTimeTab.tsx │ │ ├── RealTimeTabAdapter.tsx │ │ └── WebsiteStats.tsx ├── config.ts ├── config │ └── deploymentConfig.ts ├── constants │ └── storageKeys.ts ├── contexts │ ├── AuthContext.tsx │ ├── CustomFieldsContext.tsx │ ├── DealsContext.tsx │ ├── MasterAccountContext.tsx │ ├── TasksContext.tsx │ └── TeamContext.tsx ├── data │ └── initialData.ts ├── hooks │ ├── use-mobile.tsx │ ├── use-toast.ts │ ├── useActivityTracker.ts │ ├── useClients.ts │ ├── useContacts │ ├── useContentItems.ts │ ├── useDealsStorage.ts │ ├── useExternalIntegrations.ts │ ├── useFormError.ts │ ├── useMasterAccount.tsx │ ├── useNotifications.ts │ ├── useWebhooks.ts │ ├── useWebsiteActions.ts │ └── useWebsitePages.ts ├── index.css ├── lib │ ├── analytics.ts │ ├── api.ts │ ├── data.ts │ ├── errorHandling.ts │ └── utils.ts ├── main.tsx ├── pages │ ├── Account.tsx │ ├── Booking.tsx │ ├── Calendar.tsx │ ├── ChatbotManagement.tsx │ ├── ClientProfile.tsx │ ├── Clients.tsx │ ├── Contacts.tsx │ ├── Content.tsx │ ├── ContentScheduling.tsx │ ├── Conversations.tsx │ ├── Deals.tsx │ ├── EmailMarketing.tsx │ ├── Index.tsx │ ├── Integrations.tsx │ ├── Login.tsx │ ├── MasterAccount.tsx │ ├── NotFound.tsx │ ├── Pipeline.tsx │ ├── Projects.tsx │ ├── Reports.tsx │ ├── Reputation.tsx │ ├── Settings.tsx │ ├── SocialMediaIntegration.tsx │ └── WebsiteManagement.tsx ├── services │ ├── api.ts │ ├── auth.ts │ ├── hubspot_auth.ts │ ├── mailchimp.ts │ ├── socialMedia.ts │ └── yext.ts ├── types │ ├── masterAccount.ts │ └── website.ts ├── utils │ ├── dateUtils.ts │ ├── formatters.ts │ ├── taskUtils.ts │ ├── websitePageAdapter.ts │ └── websiteStatsCalculator.ts └── vite-env.d.ts ├── tailwind.config.ts ├── tsconfig.app.json ├── tsconfig.json ├── tsconfig.node.json └── vite.config.ts /.gitignore: -------------------------------------------------------------------------------- 1 | # Node modules 2 | node_modules/ 3 | 4 | # Build outputs 5 | dist/ 6 | build/ 7 | 8 | # Logs 9 | *.log 10 | npm-debug.log* 11 | yarn-debug.log* 12 | yarn-error.log* 13 | pnpm-debug.log* 14 | 15 | # Environment files 16 | .env 17 | .env.* # covers .env.local, .env.production, etc. 18 | 19 | # OS-specific 20 | .DS_Store 21 | Thumbs.db 22 | 23 | # IDE settings 24 | .vscode/ 25 | .idea/ 26 | 27 | # TypeScript cache 28 | *.tsbuildinfo 29 | 30 | # Misc 31 | *.local 32 | *.swp 33 | *.swo 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Welcome to your Lovable project 2 | 3 | A modern React-based frontend for the Contact Pipeline Haven application, featuring a beautiful UI built with shadcn-ui and Tailwind CSS. 4 | 5 | ## Features 6 | 7 | - Modern, responsive UI with dark mode support 8 | - Real-time activity feed 9 | - Task management and tracking 10 | - Team collaboration features 11 | - Contact management 12 | - Email integration 13 | - Settings and user preferences 14 | 15 | ## Tech Stack 16 | 17 | - **Core**: 18 | - Vite 19 | - TypeScript 20 | - React 21 | - React Router 22 | - React Query 23 | 24 | - **UI/UX**: 25 | - shadcn-ui 26 | - Tailwind CSS 27 | - Lucide Icons 28 | - React Hot Toast 29 | 30 | ## Getting Started 31 | 32 | ### Prerequisites 33 | 34 | - Node.js (v16 or higher) 35 | - npm (v7 or higher) 36 | 37 | ### Installation 38 | 39 | 1. Navigate to the frontend directory: 40 | ```sh 41 | cd frontend 42 | ``` 43 | 44 | 2. Install dependencies: 45 | ```sh 46 | npm install 47 | ``` 48 | 49 | 3. Set up environment variables: 50 | Create a `.env` file in the frontend directory: 51 | ```env 52 | VITE_API_URL=http://localhost:3000/api 53 | ``` 54 | 55 | 4. Start the development server: 56 | ```sh 57 | npm run dev 58 | ``` 59 | 60 | The application will be available at `http://localhost:5173` 61 | 62 | ## Development 63 | 64 | - Development server runs on port 5173 65 | - Hot module replacement (HMR) enabled 66 | - TypeScript for type safety 67 | - ESLint and Prettier for code formatting 68 | 69 | ## Available Scripts 70 | 71 | - `npm run dev` - Start development server 72 | - `npm run build` - Build for production 73 | - `npm run preview` - Preview production build 74 | - `npm run lint` - Run ESLint 75 | - `npm run format` - Format code with Prettier 76 | 77 | ## Project Structure 78 | 79 | ``` 80 | frontend/ 81 | ├── src/ 82 | │ ├── components/ # Reusable UI components 83 | │ ├── contexts/ # React contexts 84 | │ ├── hooks/ # Custom React hooks 85 | │ ├── pages/ # Page components 86 | │ ├── services/ # API services 87 | │ ├── styles/ # Global styles 88 | │ ├── types/ # TypeScript types 89 | │ └── utils/ # Utility functions 90 | ├── public/ # Static assets 91 | └── index.html # Entry HTML file 92 | ``` 93 | 94 | ## UI Components 95 | 96 | The project uses shadcn-ui components, which are built on top of Radix UI and styled with Tailwind CSS. Key components include: 97 | 98 | - Cards 99 | - Buttons 100 | - Forms 101 | - Modals 102 | - Toast notifications 103 | - Dropdowns 104 | - Tables 105 | 106 | ## Contributing 107 | 108 | 1. Fork the repository 109 | 2. Create your feature branch (`git checkout -b feature/amazing-feature`) 110 | 3. Commit your changes (`git commit -m 'Add some amazing feature'`) 111 | 4. Push to the branch (`git push origin feature/amazing-feature`) 112 | 5. Open a Pull Request 113 | 114 | ## License 115 | 116 | This project is licensed under the MIT License - see the LICENSE file for details. 117 | -------------------------------------------------------------------------------- /bun.lockb: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hermesdev0131/lovable-frontend-react-tailwindcss-vite/103d4ab8aee3559672c7b5fcf307d1097b6f22bc/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 | } -------------------------------------------------------------------------------- /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 | contact-pipeline-haven 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /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 | }, 13 | "dependencies": { 14 | "@hello-pangea/dnd": "^18.0.1", 15 | "@hookform/resolvers": "^3.9.0", 16 | "@hubspot/api-client": "^12.1.0", 17 | "@radix-ui/react-accordion": "^1.2.0", 18 | "@radix-ui/react-alert-dialog": "^1.1.1", 19 | "@radix-ui/react-aspect-ratio": "^1.1.0", 20 | "@radix-ui/react-avatar": "^1.1.0", 21 | "@radix-ui/react-checkbox": "^1.1.1", 22 | "@radix-ui/react-collapsible": "^1.1.0", 23 | "@radix-ui/react-context-menu": "^2.2.1", 24 | "@radix-ui/react-dialog": "^1.1.2", 25 | "@radix-ui/react-dropdown-menu": "^2.1.1", 26 | "@radix-ui/react-hover-card": "^1.1.1", 27 | "@radix-ui/react-label": "^2.1.0", 28 | "@radix-ui/react-menubar": "^1.1.1", 29 | "@radix-ui/react-navigation-menu": "^1.2.0", 30 | "@radix-ui/react-popover": "^1.1.1", 31 | "@radix-ui/react-progress": "^1.1.0", 32 | "@radix-ui/react-radio-group": "^1.2.0", 33 | "@radix-ui/react-scroll-area": "^1.1.0", 34 | "@radix-ui/react-select": "^2.1.1", 35 | "@radix-ui/react-separator": "^1.1.0", 36 | "@radix-ui/react-slider": "^1.2.0", 37 | "@radix-ui/react-slot": "^1.1.0", 38 | "@radix-ui/react-switch": "^1.1.0", 39 | "@radix-ui/react-tabs": "^1.1.0", 40 | "@radix-ui/react-toast": "^1.2.1", 41 | "@radix-ui/react-toggle": "^1.1.0", 42 | "@radix-ui/react-toggle-group": "^1.1.0", 43 | "@radix-ui/react-tooltip": "^1.1.4", 44 | "@tanstack/react-query": "^5.56.2", 45 | "@types/uuid": "^10.0.0", 46 | "axios": "^1.8.4", 47 | "class-variance-authority": "^0.7.1", 48 | "clsx": "^2.1.1", 49 | "cmdk": "^1.0.0", 50 | "date-fns": "^3.6.0", 51 | "embla-carousel-react": "^8.3.0", 52 | "input-otp": "^1.2.4", 53 | "lucide-react": "^0.462.0", 54 | "next": "^15.3.1", 55 | "next-themes": "^0.3.0", 56 | "react": "^18.3.1", 57 | "react-day-picker": "^8.10.1", 58 | "react-dom": "^18.3.1", 59 | "react-error-boundary": "^5.0.0", 60 | "react-hook-form": "^7.56.1", 61 | "react-resizable-panels": "^2.1.3", 62 | "react-router-dom": "^6.26.2", 63 | "recharts": "^2.12.7", 64 | "sonner": "^1.5.0", 65 | "tailwind-merge": "^2.5.2", 66 | "tailwindcss-animate": "^1.0.7", 67 | "uuid": "^11.1.0", 68 | "vaul": "^0.9.3", 69 | "zod": "^3.23.8" 70 | }, 71 | "devDependencies": { 72 | "@eslint/js": "^9.9.0", 73 | "@tailwindcss/typography": "^0.5.15", 74 | "@types/next": "^8.0.7", 75 | "@types/node": "^22.5.5", 76 | "@types/react": "^18.3.3", 77 | "@types/react-dom": "^18.3.0", 78 | "@vitejs/plugin-react-swc": "^3.5.0", 79 | "autoprefixer": "^10.4.21", 80 | "eslint": "^9.9.0", 81 | "eslint-plugin-react-hooks": "^5.1.0-rc.0", 82 | "eslint-plugin-react-refresh": "^0.4.9", 83 | "globals": "^15.9.0", 84 | "lovable-tagger": "^1.1.7", 85 | "postcss": "^8.5.3", 86 | "tailwindcss": "^3.4.17", 87 | "typescript": "^5.5.3", 88 | "typescript-eslint": "^8.0.1", 89 | "vite": "^5.4.1" 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hermesdev0131/lovable-frontend-react-tailwindcss-vite/103d4ab8aee3559672c7b5fcf307d1097b6f22bc/public/favicon.ico -------------------------------------------------------------------------------- /public/lovable-uploads/0e3b9242-069b-4a19-b5ad-8f96b69d7d54.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hermesdev0131/lovable-frontend-react-tailwindcss-vite/103d4ab8aee3559672c7b5fcf307d1097b6f22bc/public/lovable-uploads/0e3b9242-069b-4a19-b5ad-8f96b69d7d54.png -------------------------------------------------------------------------------- /public/lovable-uploads/19d0bac1-2f20-4dcb-8a71-c65c4635deb8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hermesdev0131/lovable-frontend-react-tailwindcss-vite/103d4ab8aee3559672c7b5fcf307d1097b6f22bc/public/lovable-uploads/19d0bac1-2f20-4dcb-8a71-c65c4635deb8.png -------------------------------------------------------------------------------- /public/lovable-uploads/2e7bc354-d939-480c-b0dc-7aa03dbde994.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hermesdev0131/lovable-frontend-react-tailwindcss-vite/103d4ab8aee3559672c7b5fcf307d1097b6f22bc/public/lovable-uploads/2e7bc354-d939-480c-b0dc-7aa03dbde994.png -------------------------------------------------------------------------------- /public/lovable-uploads/56808916-d8ae-4d5a-8aa0-f0d671bc7717.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/hermesdev0131/lovable-frontend-react-tailwindcss-vite/103d4ab8aee3559672c7b5fcf307d1097b6f22bc/public/lovable-uploads/56808916-d8ae-4d5a-8aa0-f0d671bc7717.png -------------------------------------------------------------------------------- /public/placeholder.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | User-agent: Googlebot 2 | Allow: / 3 | 4 | User-agent: Bingbot 5 | Allow: / 6 | 7 | User-agent: Twitterbot 8 | Allow: / 9 | 10 | User-agent: facebookexternalhit 11 | Allow: / 12 | 13 | User-agent: * 14 | Allow: / 15 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | 2 | #root { 3 | width: 100%; 4 | margin: 0 auto; 5 | overflow-x: hidden; 6 | } 7 | 8 | .logo { 9 | height: 6em; 10 | padding: 1.5em; 11 | will-change: filter; 12 | transition: filter 300ms; 13 | } 14 | .logo:hover { 15 | filter: drop-shadow(0 0 2em #D35400aa); 16 | } 17 | .logo.react:hover { 18 | filter: drop-shadow(0 0 2em #D35400aa); 19 | } 20 | 21 | @keyframes logo-spin { 22 | from { 23 | transform: rotate(0deg); 24 | } 25 | to { 26 | transform: rotate(360deg); 27 | } 28 | } 29 | 30 | @media (prefers-reduced-motion: no-preference) { 31 | a:nth-of-type(2) .logo { 32 | animation: logo-spin infinite 20s linear; 33 | } 34 | } 35 | 36 | .card { 37 | padding: 2em; 38 | background-color: white; 39 | color: black; 40 | } 41 | 42 | .read-the-docs { 43 | color: #888; 44 | } 45 | 46 | /* Animation for activity feed items */ 47 | @keyframes slide-in-right { 48 | 0% { 49 | transform: translateX(20px); 50 | opacity: 0; 51 | } 52 | 100% { 53 | transform: translateX(0); 54 | opacity: 1; 55 | } 56 | } 57 | 58 | .animate-slide-in-right { 59 | animation: slide-in-right 0.3s ease-out forwards; 60 | } 61 | 62 | /* Animation for deal cards */ 63 | @keyframes fade-in-up { 64 | 0% { 65 | transform: translateY(10px); 66 | opacity: 0; 67 | } 68 | 100% { 69 | transform: translateY(0); 70 | opacity: 1; 71 | } 72 | } 73 | 74 | .animate-fade-in-up { 75 | animation: fade-in-up 0.3s ease-out forwards; 76 | } 77 | 78 | /* Animation for form fields */ 79 | @keyframes fade-in { 80 | 0% { 81 | opacity: 0; 82 | } 83 | 100% { 84 | opacity: 1; 85 | } 86 | } 87 | 88 | .animate-fade-in { 89 | animation: fade-in 0.3s ease-out forwards; 90 | } 91 | 92 | /* Staggered animation for list items */ 93 | .stagger-animation > *:nth-child(1) { animation-delay: 0s; } 94 | .stagger-animation > *:nth-child(2) { animation-delay: 0.05s; } 95 | .stagger-animation > *:nth-child(3) { animation-delay: 0.1s; } 96 | .stagger-animation > *:nth-child(4) { animation-delay: 0.15s; } 97 | .stagger-animation > *:nth-child(5) { animation-delay: 0.2s; } 98 | .stagger-animation > *:nth-child(6) { animation-delay: 0.25s; } 99 | .stagger-animation > *:nth-child(7) { animation-delay: 0.3s; } 100 | .stagger-animation > *:nth-child(8) { animation-delay: 0.35s; } 101 | .stagger-animation > *:nth-child(9) { animation-delay: 0.4s; } 102 | .stagger-animation > *:nth-child(10) { animation-delay: 0.45s; } 103 | -------------------------------------------------------------------------------- /src/App.tsx: -------------------------------------------------------------------------------- 1 | import React, { use } from 'react'; 2 | //import { BrowserRouter } from 'react-router-dom'; 3 | import { ThemeProvider } from '@/components/theme/ThemeProvider'; 4 | import { MasterAccountProvider } from './contexts/MasterAccountContext'; 5 | import { CustomFieldsProvider } from './contexts/CustomFieldsContext'; 6 | import Sidebar from '@/components/layout/Sidebar'; 7 | import { BrowserRouter as Router, Routes, Route, Navigate } from 'react-router-dom'; 8 | import { Toaster } from "@/components/ui/toaster"; 9 | import { DealsProvider } from './contexts/DealsContext'; 10 | import { TasksProvider } from './contexts/TasksContext'; 11 | import { TeamProvider } from './contexts/TeamContext'; 12 | import Index from './pages/Index'; 13 | import Reputation from './pages/Reputation'; 14 | import Contacts from './pages/Contacts'; 15 | import MasterAccount from './pages/MasterAccount'; 16 | import Reports from './pages/Reports'; 17 | import ClientProfile from './pages/ClientProfile'; // Ensure correct import 18 | import Deals from './pages/Deals'; 19 | import Login from './pages/Login'; 20 | import EmailMarketing from './pages/EmailMarketing'; 21 | import Calendar from './pages/Calendar'; 22 | import WebsiteManagement from './pages/WebsiteManagement'; 23 | import Socials from './pages/Content'; 24 | import Clients from './pages/Clients'; 25 | import SettingsPage from './pages/Settings'; 26 | import ChatbotManagement from './pages/ChatbotManagement'; 27 | import { ProtectedRoute } from './components/auth/ProtectedRoute'; 28 | import { AuthProvider } from '@/contexts/AuthContext'; 29 | //import { useAuth } from '@/contexts/AuthContext'; 30 | //import { AuthCallback } from './components/auth/callback'; 31 | 32 | // MainLayout component inline since it was missing 33 | const MainLayout = ({ children }: { children: React.ReactNode }) => { 34 | const [isExpanded, setIsExpanded] = React.useState(true); 35 | 36 | const handleToggleSidebar = () => { 37 | setIsExpanded(!isExpanded); 38 | }; 39 | 40 | return ( 41 | 42 | {children} 43 | 44 | ); 45 | }; 46 | 47 | const RoutesComponent = () => { 48 | return ( 49 | 50 | 51 | } /> 52 | } /> 53 | 54 | }> 55 | } /> 56 | } /> 57 | } /> 58 | } /> 59 | } /> 60 | } /> 61 | } /> 62 | } /> 63 | } /> 64 | } /> 65 | } /> 66 | } /> 67 | {}} />} /> 68 | 69 | 70 | {/* } /> */} 71 | 72 | ); 73 | }; 74 | 75 | function App() { 76 | return ( 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | ); 99 | } 100 | 101 | 102 | export default App; 103 | -------------------------------------------------------------------------------- /src/ClientProfile.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { useRouter } from 'next/router'; 3 | 4 | const ClientProfile = () => { 5 | const router = useRouter(); 6 | const { clientId } = router.query; 7 | 8 | // Fetch client data based on clientId (this is a placeholder for actual data fetching logic) 9 | const clientData = { 10 | id: clientId, 11 | name: 'Client Name', 12 | email: 'client@example.com', 13 | phone: '123-456-7890', 14 | }; 15 | 16 | return ( 17 |
18 |

Client Profile

19 |

ID: {clientData.id}

20 |

Name: {clientData.name}

21 |

Email: {clientData.email}

22 |

Phone: {clientData.phone}

23 |
24 | ); 25 | }; 26 | 27 | export default ClientProfile; 28 | -------------------------------------------------------------------------------- /src/components/CustomErrorBoundary.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { ErrorBoundary } from 'react-error-boundary'; 4 | import { Button } from './ui/button'; 5 | 6 | function ErrorFallback({ error, resetErrorBoundary }: { error: Error; resetErrorBoundary: () => void }) { 7 | return ( 8 |
9 |
10 |

Something went wrong

11 |
12 |

An error occurred in the application.

13 |
14 |             {error.message}
15 |           
16 |
17 | 20 |
21 |
22 | ); 23 | } 24 | 25 | const CustomErrorBoundary: React.FC<{ children: React.ReactNode }> = ({ children }) => { 26 | return ( 27 | 28 | {children} 29 | 30 | ); 31 | }; 32 | 33 | export default CustomErrorBoundary; 34 | -------------------------------------------------------------------------------- /src/components/ErrorBoundary.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { ErrorBoundary as ReactErrorBoundary, FallbackProps } from 'react-error-boundary'; 4 | import { AlertCircle } from 'lucide-react'; 5 | import { Button } from "@/components/ui/button"; 6 | 7 | const ErrorFallback = ({ error, resetErrorBoundary }: FallbackProps) => { 8 | return ( 9 |
10 |
11 |
12 | 13 |
14 |

Something went wrong

15 |

16 | An error occurred in the application. Please try refreshing the page. 17 |

18 | 19 |
20 |

Error details:

21 |
22 |             {error.message}
23 |           
24 | {error.stack && ( 25 | <> 26 |

Stack trace:

27 |
28 |                 {error.stack}
29 |               
30 | 31 | )} 32 |
33 | 34 |
35 | 41 | 47 |
48 |
49 |
50 | ); 51 | }; 52 | 53 | export const ErrorBoundary: React.FC<{children: React.ReactNode}> = ({ children }) => { 54 | return ( 55 | 56 | {children} 57 | 58 | ); 59 | }; 60 | 61 | export default ErrorBoundary; 62 | -------------------------------------------------------------------------------- /src/components/auth/ProtectedRoute.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { Navigate, Outlet, useLocation } from 'react-router-dom'; 3 | import { useAuth } from '@/contexts/AuthContext'; 4 | import { toast } from '@/hooks/use-toast'; 5 | 6 | export const ProtectedRoute = () => { 7 | const { authState } = useAuth(); 8 | const location = useLocation(); 9 | 10 | if (!authState.isAuthenticated) { 11 | const isRedirectFromLogin = location.state?.from?.pathname === '/login'; 12 | 13 | if (location.pathname !== '/login' && location.pathname !== '/' && !isRedirectFromLogin) { 14 | toast({ 15 | title: "Authentication Required", 16 | description: "Please log in to access this page.", 17 | variant: "destructive" 18 | }); 19 | } 20 | return ; 21 | } 22 | 23 | if (authState.user && authState.user.role) { 24 | const { role } = authState.user; 25 | const adminOnlyPaths = ['/master-account']; 26 | const editorRestrictedPaths = ['/settings']; 27 | 28 | if (role === 'viewer' && (adminOnlyPaths.includes(location.pathname))) { 29 | toast({ 30 | title: "Access Denied", 31 | description: "You don't have permission to access this page", 32 | variant: "destructive" 33 | }); 34 | return ; 35 | } 36 | 37 | if (role === 'editor' && adminOnlyPaths.includes(location.pathname)) { 38 | toast({ 39 | title: "Access Denied", 40 | description: "Admin access required for this page", 41 | variant: "destructive" 42 | }); 43 | return ; 44 | } 45 | } 46 | 47 | // If we reach here, the user is authenticated and has the right permissions 48 | return ; 49 | } 50 | -------------------------------------------------------------------------------- /src/components/auth/callback.tsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from 'react'; 2 | import { useNavigate, useSearchParams } from 'react-router-dom'; 3 | import { hubspotAuthService } from '@/services/hubspot_auth'; 4 | import { useToast } from '@/hooks/use-toast'; 5 | 6 | export const AuthCallback = () => { 7 | const [searchParams] = useSearchParams(); 8 | const navigate = useNavigate(); 9 | const { toast } = useToast(); 10 | 11 | useEffect(() => { 12 | const code = searchParams.get('code'); 13 | const error = searchParams.get('error'); 14 | 15 | if (error) { 16 | toast({ 17 | title: "Authentication Error", 18 | description: `Failed to authenticate with HubSpot: ${error}`, 19 | variant: "destructive" 20 | }); 21 | navigate('/login'); 22 | return; 23 | } 24 | 25 | if (!code) { 26 | toast({ 27 | title: "Authentication Error", 28 | description: "No authorization code received", 29 | variant: "destructive" 30 | }); 31 | navigate('/login'); 32 | return; 33 | } 34 | 35 | const handleAuth = async () => { 36 | try { 37 | await hubspotAuthService.login(code); 38 | const user = await hubspotAuthService.getUser(); 39 | 40 | toast({ 41 | title: "Success", 42 | description: `Successfully connected to HubSpot as ${user.email}` 43 | }); 44 | 45 | navigate('/'); 46 | } catch (error) { 47 | console.error('Authentication error:', error); 48 | toast({ 49 | title: "Authentication Error", 50 | description: "Failed to authenticate with Hubspot", 51 | variant: "destructive" 52 | }); 53 | navigate('/login'); 54 | } 55 | }; 56 | 57 | handleAuth(); 58 | }, [searchParams, navigate, toast]); 59 | 60 | return ( 61 |
62 |
63 |

Connecting to HubSpot...

64 |

Please wait while we authenticate your account.

65 |
66 |
67 | ); 68 | }; -------------------------------------------------------------------------------- /src/components/calendar/CalendarIntegrationDialog.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { Dialog, DialogContent, DialogTrigger } from '@/components/ui/dialog'; 4 | import { Button } from '@/components/ui/button'; 5 | import { Calendar } from 'lucide-react'; 6 | import CalendarIntegration from './CalendarIntegration'; 7 | 8 | interface CalendarIntegrationDialogProps { 9 | onSync?: () => void; 10 | triggerButtonText?: string; 11 | triggerButtonVariant?: 'default' | 'outline' | 'secondary' | 'ghost' | 'link' | 'destructive'; 12 | } 13 | 14 | const CalendarIntegrationDialog: React.FC = ({ 15 | onSync = () => {}, 16 | triggerButtonText = "Connect Calendar", 17 | triggerButtonVariant = "default" 18 | }) => { 19 | const [open, setOpen] = React.useState(false); 20 | 21 | const handleSync = () => { 22 | onSync(); 23 | }; 24 | 25 | const handleClose = () => { 26 | setOpen(false); 27 | }; 28 | 29 | return ( 30 | 31 | 32 | 36 | 37 | 38 | 42 | 43 | 44 | ); 45 | }; 46 | 47 | export default CalendarIntegrationDialog; 48 | -------------------------------------------------------------------------------- /src/components/chatbot/ChatDrawer.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React, { useState } from 'react'; 3 | import { Bot, X } from 'lucide-react'; 4 | import { Button } from '@/components/ui/button'; 5 | import { Drawer, DrawerContent, DrawerTrigger } from '@/components/ui/drawer'; 6 | import { ChatbotUI } from './ChatbotUI'; 7 | import { useActivityTracker } from '@/hooks/useActivityTracker'; 8 | 9 | interface ChatDrawerProps { 10 | knowledgeBase: string[]; 11 | onAddKnowledge?: (knowledge: string) => void; 12 | } 13 | 14 | export function ChatDrawer({ knowledgeBase, onAddKnowledge }: ChatDrawerProps) { 15 | const [open, setOpen] = useState(false); 16 | const { trackChatbotInteraction } = useActivityTracker(); 17 | 18 | const handleAddKnowledge = (knowledge: string) => { 19 | if (onAddKnowledge) { 20 | onAddKnowledge(knowledge); 21 | } 22 | }; 23 | 24 | // Track when user opens the chatbot 25 | const handleOpenChange = (isOpen: boolean) => { 26 | setOpen(isOpen); 27 | if (isOpen) { 28 | trackChatbotInteraction("Opened chatbot"); 29 | } 30 | }; 31 | 32 | return ( 33 | 34 | 35 | 41 | 42 | 43 |
44 |
45 | 50 |
51 | 52 | 53 | ); 54 | } 55 | -------------------------------------------------------------------------------- /src/components/compatibility/BrowserCompatibilityCheck.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React, { useEffect, useState } from 'react'; 3 | import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert"; 4 | import { AlertTriangle } from "lucide-react"; 5 | 6 | const BrowserCompatibilityCheck: React.FC = () => { 7 | const [isCompatible, setIsCompatible] = useState(true); 8 | const [browserInfo, setBrowserInfo] = useState(''); 9 | 10 | useEffect(() => { 11 | const checkBrowserCompatibility = () => { 12 | const userAgent = navigator.userAgent.toLowerCase(); 13 | 14 | // Get browser information for display 15 | let browserName = 'Unknown Browser'; 16 | if (userAgent.indexOf('chrome') > -1) browserName = 'Google Chrome'; 17 | else if (userAgent.indexOf('firefox') > -1) browserName = 'Firefox'; 18 | else if (userAgent.indexOf('safari') > -1 && userAgent.indexOf('chrome') === -1) browserName = 'Safari'; 19 | else if (userAgent.indexOf('edge') > -1 || userAgent.indexOf('edg') > -1) browserName = 'Microsoft Edge'; 20 | else if (userAgent.indexOf('msie') > -1 || userAgent.indexOf('trident') > -1) browserName = 'Internet Explorer'; 21 | 22 | // Set browser info for display 23 | setBrowserInfo(browserName); 24 | 25 | // Check for Internet Explorer 26 | const isIE = userAgent.indexOf('msie') > -1 || userAgent.indexOf('trident') > -1; 27 | 28 | // Check for very old browsers that don't support modern JS features 29 | const isOldBrowser = !window.Promise || !window.fetch; 30 | 31 | setIsCompatible(!isIE && !isOldBrowser); 32 | }; 33 | 34 | checkBrowserCompatibility(); 35 | }, []); 36 | 37 | if (isCompatible) { 38 | return null; 39 | } 40 | 41 | return ( 42 | 43 | 44 | Browser Compatibility Issue 45 | 46 | Your browser ({browserInfo}) may not be fully compatible with this application. 47 | For the best experience, please use a modern browser like Google Chrome, Firefox, Microsoft Edge, or Safari. 48 | 49 | 50 | ); 51 | }; 52 | 53 | export default BrowserCompatibilityCheck; 54 | -------------------------------------------------------------------------------- /src/components/content/ContentEditDialog.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React, { useState } from 'react'; 3 | import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from "@/components/ui/dialog"; 4 | import { Button } from "@/components/ui/button"; 5 | import ContentCreationForm from './ContentCreationForm'; 6 | import { useIsMobile } from "@/hooks/use-mobile"; 7 | import { ContentItem } from "@/types/masterAccount"; 8 | import { toast } from "@/hooks/use-toast"; 9 | import { logError } from "@/lib/errorHandling"; 10 | import { Sheet, SheetContent, SheetDescription, SheetHeader, SheetTitle } from "@/components/ui/sheet"; 11 | 12 | interface ContentEditDialogProps { 13 | isOpen: boolean; 14 | onClose: () => void; 15 | contentItem: ContentItem | null; 16 | } 17 | 18 | const ContentEditDialog: React.FC = ({ 19 | isOpen, 20 | onClose, 21 | contentItem 22 | }) => { 23 | const isMobile = useIsMobile(); 24 | const [isSubmitting, setIsSubmitting] = useState(false); 25 | 26 | if (!contentItem) return null; 27 | 28 | const handleSuccess = () => { 29 | toast({ 30 | title: "Content Updated", 31 | description: "Your content has been updated successfully." 32 | }); 33 | onClose(); 34 | }; 35 | 36 | const handleError = (error: unknown) => { 37 | setIsSubmitting(false); 38 | logError(error, "Failed to update content"); 39 | }; 40 | 41 | // Use Sheet component for tablet-sized devices 42 | if (isMobile) { 43 | return ( 44 | 45 | 46 | 47 | Edit Content 48 | 49 | Make changes to your content 50 | 51 | 52 |
53 | 60 |
61 |
62 | 65 |
66 |
67 |
68 | ); 69 | } 70 | 71 | return ( 72 | 73 | 74 | 75 | Edit Content 76 | 77 | Make changes to your content item 78 | 79 | 80 | 87 | 88 | 89 | ); 90 | }; 91 | 92 | export default ContentEditDialog; 93 | -------------------------------------------------------------------------------- /src/components/content/ContentScheduling.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; 3 | import { formatDate } from '@/utils/formatters'; 4 | import { useMasterAccount } from '@/contexts/MasterAccountContext'; 5 | 6 | // This is just to make sure we have a formatter function available 7 | // in case it's needed for handling any potentially undefined dates 8 | const safeFormatDate = (dateString?: string | null) => { 9 | if (!dateString) return ''; 10 | return formatDate(dateString); 11 | }; 12 | 13 | const ContentScheduling = () => { 14 | const { contentItems } = useMasterAccount(); 15 | 16 | // Rest of the component implementation 17 | return ( 18 | 19 | 20 | Content Scheduling 21 | 22 | 23 | {/* Your content scheduling UI */} 24 |
25 | {contentItems.length === 0 ? ( 26 |

No content items scheduled yet.

27 | ) : ( 28 |
    29 | {contentItems.map(item => ( 30 |
  • 31 | {item.title} - {item.scheduledFor ? safeFormatDate(item.scheduledFor) : 'Not scheduled'} 32 |
  • 33 | ))} 34 |
35 | )} 36 |
37 |
38 |
39 | ); 40 | }; 41 | 42 | export default ContentScheduling; 43 | -------------------------------------------------------------------------------- /src/components/dashboard/DashboardStats.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Users, LineChart, PieChart } from 'lucide-react'; 3 | import StatCard from './StatCard'; 4 | import { formatCurrency } from '@/utils/formatters'; 5 | import { useAuth } from '@/contexts/AuthContext'; 6 | 7 | interface DashboardStatsProps { 8 | totalContacts: number; 9 | openDeals: number; 10 | totalDealValue: number; 11 | onCardClick: (title: string, path: string) => void; 12 | isLoading?: boolean; 13 | } 14 | 15 | const DashboardStats: React.FC = ({ 16 | totalContacts, 17 | openDeals, 18 | totalDealValue, 19 | onCardClick, 20 | isLoading = false 21 | }) => { 22 | const { authState } = useAuth(); 23 | const isAuthenticated = authState?.isAuthenticated ?? false; 24 | 25 | const handleCardClick = (title: string, path: string) => { 26 | if (!isAuthenticated) return; 27 | onCardClick(title, path); 28 | }; 29 | 30 | return ( 31 |
32 | handleCardClick("Contacts", "/clients")} 38 | isLoading={isLoading} 39 | disabled={!isAuthenticated} 40 | /> 41 | 42 | handleCardClick("Deals", "/deals")} 48 | isLoading={isLoading} 49 | disabled={!isAuthenticated} 50 | /> 51 | 52 | handleCardClick("Opportunities", "/reports")} 58 | isLoading={isLoading} 59 | disabled={!isAuthenticated} 60 | /> 61 |
62 | ); 63 | }; 64 | 65 | export default DashboardStats; 66 | -------------------------------------------------------------------------------- /src/components/dashboard/DealsOverview.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'; 3 | import { PieChart, Pie, Cell, ResponsiveContainer, Legend, Tooltip } from 'recharts'; 4 | import { Button } from '@/components/ui/button'; 5 | import { useNavigate } from 'react-router-dom'; 6 | import { Plus } from 'lucide-react'; 7 | import { useAuth } from '@/contexts/AuthContext'; 8 | 9 | interface ChartData { 10 | name: string; 11 | value: number; 12 | color: string; 13 | } 14 | 15 | interface DealsOverviewProps { 16 | dealStageData: ChartData[]; 17 | hasDeals: boolean; 18 | } 19 | 20 | const DealsOverview: React.FC = ({ dealStageData, hasDeals }) => { 21 | const navigate = useNavigate(); 22 | const { authState } = useAuth(); 23 | const isAuthenticated = authState?.isAuthenticated ?? false; 24 | 25 | const handleCreateDeal = () => { 26 | if (!isAuthenticated) return; 27 | navigate('/deals'); 28 | }; 29 | 30 | return ( 31 | 34 | 35 | Deals Overview 36 | 37 | 38 | {hasDeals ? ( 39 | 40 | 41 | 51 | {dealStageData.map((entry, index) => ( 52 | 53 | ))} 54 | 55 | ( 60 | {value} 61 | )} 62 | /> 63 | [value, name]} 65 | contentStyle={{ 66 | backgroundColor: 'rgba(255, 255, 255, 0.9)', 67 | border: '1px solid #ccc', 68 | borderRadius: '4px', 69 | padding: '8px' 70 | }} 71 | /> 72 | 73 | 74 | ) : ( 75 |
76 |

No deals data available

77 | 86 |
87 | )} 88 |
89 |
90 | ); 91 | }; 92 | 93 | export default DealsOverview; 94 | -------------------------------------------------------------------------------- /src/components/dashboard/StatCard.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card'; 3 | import { Loader2 } from 'lucide-react'; 4 | 5 | interface StatCardProps { 6 | title: string; 7 | value: number | string; 8 | icon: React.ElementType; 9 | subtitle: string; 10 | onClick?: () => void; 11 | isLoading?: boolean; 12 | disabled?: boolean; 13 | } 14 | 15 | const StatCard = ({ 16 | title, 17 | value, 18 | icon: Icon, 19 | subtitle, 20 | onClick, 21 | isLoading = false, 22 | disabled = false 23 | }: StatCardProps) => { 24 | return ( 25 | 33 | 34 | {title} 35 | 36 | 37 |
38 |
39 | {isLoading ? ( 40 |
41 | 42 | Loading... 43 |
44 | ) : ( 45 | value 46 | )} 47 |
48 |
49 | {isLoading ? ( 50 | 51 | ) : ( 52 | 53 | )} 54 |
55 |
56 |
57 | 58 | {isLoading ? "Loading data..." : subtitle} 59 | 60 |
61 |
62 |
63 | ); 64 | }; 65 | 66 | export default StatCard; 67 | -------------------------------------------------------------------------------- /src/components/deals/EditDealDialog.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { Dialog, DialogContent } from "@/components/ui/dialog"; 4 | import { Deal, Stage } from './types'; 5 | import { TeamMember } from '@/components/settings/TeamMembers'; 6 | import DealForm, { DealFormField } from './DealForm'; 7 | 8 | interface EditDealDialogProps { 9 | isOpen: boolean; 10 | onClose: () => void; 11 | deal: Deal | null; 12 | onSave: (updatedDeal: Deal) => void; 13 | stages: Stage[]; 14 | teamMembers: TeamMember[]; 15 | customFields?: DealFormField[]; 16 | isLoading?: boolean; 17 | } 18 | 19 | const EditDealDialog: React.FC = ({ 20 | isOpen, 21 | onClose, 22 | deal, 23 | onSave, 24 | stages, 25 | teamMembers, 26 | customFields = [], 27 | isLoading = false 28 | }) => { 29 | if (!deal) return null; 30 | 31 | const handleSave = (updatedDealData: Partial) => { 32 | const updatedDeal = { 33 | ...deal, 34 | ...updatedDealData, 35 | updatedAt: new Date().toISOString() 36 | }; 37 | 38 | onSave(updatedDeal); 39 | }; 40 | 41 | return ( 42 | 43 | 44 | 53 | 54 | 55 | ); 56 | }; 57 | 58 | export default EditDealDialog; 59 | -------------------------------------------------------------------------------- /src/components/deals/types.ts: -------------------------------------------------------------------------------- 1 | 2 | export interface Deal { 3 | id: string; 4 | name: string; 5 | company: string; 6 | value: number; 7 | currency: string; 8 | probability: number; 9 | stage: string; 10 | closingDate: string; 11 | description: string; 12 | assignedTo?: string; 13 | contactId?: string; 14 | expectedCloseDate?: string; 15 | createdAt: string; 16 | updatedAt: string; 17 | customFields?: Record; 18 | attachments?: Array<{ 19 | name: string; 20 | size: number; 21 | type: string; 22 | lastModified: number; 23 | }>; 24 | appointments?: Array<{ 25 | title: string; 26 | datetime: string; 27 | }>; 28 | } 29 | 30 | export interface Stage { 31 | id: string; 32 | label: string; 33 | } 34 | 35 | export interface Column { 36 | id: string; 37 | label: string; 38 | } 39 | 40 | export type DealStage = string; 41 | 42 | export const DEFAULT_COLUMNS: Column[] = [ 43 | { id: 'discovery', label: 'Discovery' }, 44 | { id: 'proposal', label: 'Proposal' }, 45 | { id: 'negotiation', label: 'Negotiation' }, 46 | { id: 'decision', label: 'Decision' }, 47 | { id: 'contract_sent', label: 'Contract Sent' }, 48 | { id: 'closed_won', label: 'Closed Won' }, 49 | { id: 'closed_lost', label: 'Closed Lost' } 50 | 51 | 52 | ]; 53 | 54 | 55 | 56 | export const STORAGE_KEYS = { 57 | DEALS_COLUMNS: 'crm_deals_columns', 58 | DEALS_DATA: 'crm_deals_data' 59 | }; 60 | -------------------------------------------------------------------------------- /src/components/deployment/DeploymentChecklist.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; 4 | import { Checkbox } from "@/components/ui/checkbox"; 5 | import { Label } from "@/components/ui/label"; 6 | import { IS_PRODUCTION } from '@/config/deploymentConfig'; 7 | 8 | // Only show this component in development mode 9 | const DeploymentChecklist: React.FC = () => { 10 | if (IS_PRODUCTION) { 11 | return null; 12 | } 13 | 14 | return ( 15 | 16 | 17 | Deployment Checklist 18 | 19 | Items to verify before deploying to production. This component only appears in development mode. 20 | 21 | 22 | 23 |
24 |
25 | 26 |
27 | 28 |

29 | Ensure all API endpoints are pointing to production URLs in the deploymentConfig.ts file. 30 |

31 |
32 |
33 | 34 |
35 | 36 |
37 | 38 |

39 | Ensure no test accounts or dummy data will be visible in production. 40 |

41 |
42 |
43 | 44 |
45 | 46 |
47 | 48 |

49 | Test on various screen sizes to ensure proper responsiveness. 50 |

51 |
52 |
53 | 54 |
55 | 56 |
57 | 58 |

59 | Verify the application works in Chrome, Firefox, Safari, and Edge. 60 |

61 |
62 |
63 | 64 |
65 | 66 |
67 | 68 |

69 | Set analyticsEnabled to true in the production configuration. 70 |

71 |
72 |
73 |
74 |
75 |
76 | ); 77 | }; 78 | 79 | export default DeploymentChecklist; 80 | -------------------------------------------------------------------------------- /src/components/master-account/ClientDirectory.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; 4 | import { Button } from "@/components/ui/button"; 5 | import { Trash2, Mail, Phone } from "lucide-react"; 6 | import { useMasterAccount } from "@/contexts/MasterAccountContext"; 7 | import { Badge } from "@/components/ui/badge"; 8 | 9 | const ClientDirectory = () => { 10 | const { clients, removeClient } = useMasterAccount(); 11 | 12 | const deleteClient = (id: number) => { 13 | removeClient(id); 14 | }; 15 | 16 | if (clients.length === 0) { 17 | return ( 18 |
19 |

No clients have been added yet

20 | 23 |
24 | ); 25 | } 26 | 27 | return ( 28 | 29 | 30 | 31 | Client Name 32 | Contact 33 | Company 34 | Lead Type 35 | Tags 36 | Actions 37 | 38 | 39 | 40 | {clients.map((client) => ( 41 | 42 | {client.firstName} {client.lastName} 43 | 44 |
45 | {client.emails[0] && ( 46 |
47 | 48 | {client.emails[0]} 49 |
50 | )} 51 | {client.phoneNumbers[0] && ( 52 |
53 | 54 | {client.phoneNumbers[0]} 55 |
56 | )} 57 |
58 |
59 | {client.company} 60 | {client.leadType} 61 | 62 |
63 | {client.tags && client.tags.map(tag => ( 64 | {tag} 65 | ))} 66 |
67 |
68 | 69 | 77 | 78 |
79 | ))} 80 |
81 |
82 | ); 83 | }; 84 | 85 | export default ClientDirectory; 86 | -------------------------------------------------------------------------------- /src/components/master-account/ClientPerformanceTable.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; 4 | import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; 5 | import { Button } from "@/components/ui/button"; 6 | import { Building2 } from "lucide-react"; 7 | import { useMasterAccount } from "@/contexts/MasterAccountContext"; 8 | 9 | interface SalesData { 10 | name: string; 11 | sales: number; 12 | leads: number; 13 | conversions: number; 14 | } 15 | 16 | interface ClientPerformanceTableProps { 17 | clientSalesData: SalesData[]; 18 | } 19 | 20 | const ClientPerformanceTable = ({ clientSalesData }: ClientPerformanceTableProps) => { 21 | const { clients } = useMasterAccount(); 22 | 23 | if (clients.length === 0) { 24 | return ( 25 | 26 | 27 | Detailed Client Performance 28 | Sales and conversion metrics for all clients 29 | 30 | 31 |
32 |

No clients have been added yet

33 | 37 |
38 |
39 |
40 | ); 41 | } 42 | 43 | return ( 44 | 45 | 46 | Detailed Client Performance 47 | Sales and conversion metrics for all clients 48 | 49 | 50 | 51 | 52 | 53 | Client Name 54 | Sales 55 | Leads 56 | Conversions 57 | Rate 58 | Status 59 | 60 | 61 | 62 | {clientSalesData.map((data, index) => { 63 | const client = clients[index]; 64 | if (!client) return null; 65 | 66 | return ( 67 | 68 | {`${client.firstName} ${client.lastName}`} 69 | ${data.sales.toLocaleString()} 70 | {data.leads} 71 | {data.conversions} 72 | {Math.round((data.conversions / data.leads) * 100)}% 73 | 74 | 81 | {client.leadType} 82 | 83 | 84 | 85 | ); 86 | })} 87 | 88 |
89 |
90 |
91 | ); 92 | }; 93 | 94 | export default ClientPerformanceTable; 95 | -------------------------------------------------------------------------------- /src/components/master-account/ClientSalesChart.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"; 4 | import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, Legend, ResponsiveContainer } from 'recharts'; 5 | 6 | interface SalesData { 7 | name: string; 8 | sales: number; 9 | leads: number; 10 | conversions: number; 11 | } 12 | 13 | interface ClientSalesChartProps { 14 | clientSalesData: SalesData[]; 15 | } 16 | 17 | const ClientSalesChart = ({ clientSalesData }: ClientSalesChartProps) => { 18 | return ( 19 | 20 | 21 | Client Sales Overview 22 | Monthly sales performance by client 23 | 24 | 25 |
26 | 27 | 36 | 37 | 38 | 39 | ['$' + value.toLocaleString(), 'Sales']} /> 40 | 41 | 42 | 43 | 44 |
45 |
46 |
47 | ); 48 | }; 49 | 50 | export default ClientSalesChart; 51 | -------------------------------------------------------------------------------- /src/components/master-account/SalesSummaryCards.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React from 'react'; 3 | import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"; 4 | 5 | interface SalesSummaryCardsProps { 6 | totalSales: number; 7 | averageSales: number; 8 | totalLeads: number; 9 | totalConversions: number; 10 | } 11 | 12 | const SalesSummaryCards = ({ totalSales, averageSales, totalLeads, totalConversions }: SalesSummaryCardsProps) => { 13 | return ( 14 |
15 | 16 | 17 | Total Client Sales 18 | 19 | 20 |
${totalSales.toLocaleString()}
21 |

Across all clients

22 |
23 |
24 | 25 | 26 | Average Sales per Client 27 | 28 | 29 |
${Math.round(averageSales).toLocaleString()}
30 |

Monthly average

31 |
32 |
33 | 34 | 35 | Total Leads / Conversions 36 | 37 | 38 |
{totalLeads} / {totalConversions}
39 |

40 | {Math.round((totalConversions / totalLeads) * 100)}% conversion rate 41 |

42 |
43 |
44 |
45 | ); 46 | }; 47 | 48 | export default SalesSummaryCards; 49 | -------------------------------------------------------------------------------- /src/components/theme/ThemeProvider.tsx: -------------------------------------------------------------------------------- 1 | 2 | import React, { createContext, useContext, useEffect, useState } from "react"; 3 | 4 | type Theme = "light" | "dark" | "system"; 5 | 6 | type ThemeProviderProps = { 7 | children: React.ReactNode; 8 | defaultTheme?: Theme; 9 | storageKey?: string; 10 | }; 11 | 12 | type ThemeProviderState = { 13 | theme: Theme; 14 | setTheme: (theme: Theme) => void; 15 | }; 16 | 17 | const initialState: ThemeProviderState = { 18 | theme: "system", 19 | setTheme: () => null, 20 | }; 21 | 22 | const ThemeProviderContext = createContext(initialState); 23 | 24 | export function ThemeProvider({ 25 | children, 26 | defaultTheme = "system", 27 | storageKey = "vite-ui-theme", 28 | ...props 29 | }: ThemeProviderProps) { 30 | const [theme, setTheme] = useState( 31 | () => (localStorage.getItem(storageKey) as Theme) || defaultTheme 32 | ); 33 | 34 | useEffect(() => { 35 | const root = window.document.documentElement; 36 | root.classList.remove("light", "dark"); 37 | 38 | if (theme === "system") { 39 | const systemTheme = window.matchMedia("(prefers-color-scheme: dark)") 40 | .matches 41 | ? "dark" 42 | : "light"; 43 | 44 | root.classList.add(systemTheme); 45 | return; 46 | } 47 | 48 | root.classList.add(theme); 49 | }, [theme]); 50 | 51 | const value = { 52 | theme, 53 | setTheme: (theme: Theme) => { 54 | localStorage.setItem(storageKey, theme); 55 | setTheme(theme); 56 | }, 57 | }; 58 | 59 | return ( 60 | 61 | {children} 62 | 63 | ); 64 | } 65 | 66 | export const useTheme = () => { 67 | const context = useContext(ThemeProviderContext); 68 | 69 | if (context === undefined) 70 | throw new Error("useTheme must be used within a ThemeProvider"); 71 | 72 | return context; 73 | }; 74 | -------------------------------------------------------------------------------- /src/components/theme/ThemeToggle.tsx: -------------------------------------------------------------------------------- 1 | 2 | import { MoonStar, Sun } from "lucide-react"; 3 | import { useTheme } from "./ThemeProvider"; 4 | import { Button } from "@/components/ui/button"; 5 | import { DropdownMenuItem } from "@/components/ui/dropdown-menu"; 6 | 7 | interface ThemeToggleProps { 8 | variant?: "button" | "dropdown"; 9 | } 10 | 11 | export function ThemeToggle({ variant = "button" }: ThemeToggleProps) { 12 | const { theme, setTheme } = useTheme(); 13 | 14 | const toggleTheme = () => { 15 | setTheme(theme === "dark" ? "light" : "dark"); 16 | }; 17 | 18 | if (variant === "dropdown") { 19 | return ( 20 |
24 | {theme === "dark" ? ( 25 | <> 26 | 27 | Light Mode 28 | 29 | ) : ( 30 | <> 31 | 32 | Dark Mode 33 | 34 | )} 35 |
36 | ); 37 | } 38 | 39 | return ( 40 | 47 | ); 48 | } 49 | -------------------------------------------------------------------------------- /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) =>