@crmy/web 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/index.html +23 -0
  2. package/package.json +76 -0
  3. package/postcss.config.js +6 -0
  4. package/public/android-chrome-192x192.png +0 -0
  5. package/public/android-chrome-512x512.png +0 -0
  6. package/public/apple-touch-icon.png +0 -0
  7. package/public/favicon-16x16.png +0 -0
  8. package/public/favicon-32x32.png +0 -0
  9. package/public/favicon.ico +0 -0
  10. package/public/favicon.svg +13 -0
  11. package/public/site.webmanifest +1 -0
  12. package/src/App.tsx +158 -0
  13. package/src/api/client.ts +82 -0
  14. package/src/api/hooks.ts +689 -0
  15. package/src/assets/crmy-logo.png +0 -0
  16. package/src/components/CustomFields.tsx +240 -0
  17. package/src/components/NavLink.tsx +28 -0
  18. package/src/components/crm/AIFab.tsx +37 -0
  19. package/src/components/crm/AccountDrawer.tsx +372 -0
  20. package/src/components/crm/ActivityTimeline.tsx +115 -0
  21. package/src/components/crm/AssignmentDrawer.tsx +396 -0
  22. package/src/components/crm/BriefingPanel.tsx +217 -0
  23. package/src/components/crm/CommandPalette.tsx +254 -0
  24. package/src/components/crm/ContactAvatar.tsx +49 -0
  25. package/src/components/crm/ContactDrawer.tsx +438 -0
  26. package/src/components/crm/ContextPanel.tsx +200 -0
  27. package/src/components/crm/CrmWidgets.tsx +417 -0
  28. package/src/components/crm/DrawerShell.tsx +77 -0
  29. package/src/components/crm/ListToolbar.tsx +252 -0
  30. package/src/components/crm/OpportunityDrawer.tsx +372 -0
  31. package/src/components/crm/PaginationBar.tsx +111 -0
  32. package/src/components/crm/QuickAddDrawer.tsx +652 -0
  33. package/src/components/crm/ShortcutsOverlay.tsx +65 -0
  34. package/src/components/crm/UseCaseDrawer.tsx +454 -0
  35. package/src/components/layout/MobileNav.tsx +49 -0
  36. package/src/components/layout/Sidebar.tsx +157 -0
  37. package/src/components/layout/TopBar.tsx +54 -0
  38. package/src/components/settings/ActorsSettings.tsx +1190 -0
  39. package/src/components/ui/accordion.tsx +52 -0
  40. package/src/components/ui/alert-dialog.tsx +104 -0
  41. package/src/components/ui/alert.tsx +43 -0
  42. package/src/components/ui/aspect-ratio.tsx +5 -0
  43. package/src/components/ui/avatar.tsx +38 -0
  44. package/src/components/ui/badge.tsx +29 -0
  45. package/src/components/ui/breadcrumb.tsx +90 -0
  46. package/src/components/ui/button.tsx +47 -0
  47. package/src/components/ui/calendar.tsx +54 -0
  48. package/src/components/ui/card.tsx +43 -0
  49. package/src/components/ui/carousel.tsx +224 -0
  50. package/src/components/ui/chart.tsx +303 -0
  51. package/src/components/ui/checkbox.tsx +26 -0
  52. package/src/components/ui/collapsible.tsx +9 -0
  53. package/src/components/ui/command.tsx +132 -0
  54. package/src/components/ui/context-menu.tsx +178 -0
  55. package/src/components/ui/date-picker.tsx +313 -0
  56. package/src/components/ui/dialog.tsx +95 -0
  57. package/src/components/ui/drawer.tsx +87 -0
  58. package/src/components/ui/dropdown-menu.tsx +179 -0
  59. package/src/components/ui/form.tsx +129 -0
  60. package/src/components/ui/hover-card.tsx +27 -0
  61. package/src/components/ui/input-otp.tsx +61 -0
  62. package/src/components/ui/input.tsx +22 -0
  63. package/src/components/ui/label.tsx +17 -0
  64. package/src/components/ui/menubar.tsx +207 -0
  65. package/src/components/ui/navigation-menu.tsx +120 -0
  66. package/src/components/ui/pagination.tsx +81 -0
  67. package/src/components/ui/popover.tsx +29 -0
  68. package/src/components/ui/progress.tsx +23 -0
  69. package/src/components/ui/radio-group.tsx +36 -0
  70. package/src/components/ui/resizable.tsx +37 -0
  71. package/src/components/ui/scroll-area.tsx +38 -0
  72. package/src/components/ui/select.tsx +143 -0
  73. package/src/components/ui/separator.tsx +20 -0
  74. package/src/components/ui/sheet.tsx +107 -0
  75. package/src/components/ui/sidebar.tsx +637 -0
  76. package/src/components/ui/skeleton.tsx +7 -0
  77. package/src/components/ui/slider.tsx +23 -0
  78. package/src/components/ui/sonner.tsx +24 -0
  79. package/src/components/ui/switch.tsx +27 -0
  80. package/src/components/ui/table.tsx +72 -0
  81. package/src/components/ui/tabs.tsx +53 -0
  82. package/src/components/ui/textarea.tsx +21 -0
  83. package/src/components/ui/toast.tsx +111 -0
  84. package/src/components/ui/toaster.tsx +24 -0
  85. package/src/components/ui/toggle-group.tsx +49 -0
  86. package/src/components/ui/toggle.tsx +37 -0
  87. package/src/components/ui/tooltip.tsx +28 -0
  88. package/src/components/ui/use-toast.ts +1 -0
  89. package/src/components/ui/utils.ts +9 -0
  90. package/src/contexts/AgentSettingsContext.tsx +24 -0
  91. package/src/hooks/use-mobile.tsx +19 -0
  92. package/src/hooks/use-toast.ts +186 -0
  93. package/src/hooks/useKeyboardShortcuts.ts +95 -0
  94. package/src/hooks/useTheme.ts +24 -0
  95. package/src/index.css +245 -0
  96. package/src/lib/entityColors.ts +18 -0
  97. package/src/lib/stageConfig.ts +32 -0
  98. package/src/lib/utils.ts +6 -0
  99. package/src/main.tsx +25 -0
  100. package/src/pages/Accounts.tsx +205 -0
  101. package/src/pages/Activities.tsx +251 -0
  102. package/src/pages/Agent.tsx +237 -0
  103. package/src/pages/AgentSettings.tsx +544 -0
  104. package/src/pages/Assignments.tsx +750 -0
  105. package/src/pages/Contacts.tsx +200 -0
  106. package/src/pages/Dashboard.tsx +143 -0
  107. package/src/pages/Inbox.tsx +615 -0
  108. package/src/pages/NotFound.tsx +24 -0
  109. package/src/pages/Opportunities.tsx +386 -0
  110. package/src/pages/SearchResults.tsx +49 -0
  111. package/src/pages/Settings.tsx +1884 -0
  112. package/src/pages/UseCases.tsx +396 -0
  113. package/src/pages/auth/Login.tsx +261 -0
  114. package/src/pages/hitl/HITL.tsx +101 -0
  115. package/src/store/appStore.ts +103 -0
  116. package/src/vite-env.d.ts +14 -0
  117. package/tailwind.config.js +121 -0
  118. package/tsconfig.json +24 -0
  119. package/vite.config.ts +27 -0
package/index.html ADDED
@@ -0,0 +1,23 @@
1
+ <!doctype html>
2
+ <html lang="en" class="dark">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
6
+ <title>CRMy — AI-Native CRM for Agents</title>
7
+ <meta name="description" content="CRMy is an open-source, AI-agent-native CRM." />
8
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
9
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
10
+ <link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;500;600;700;800&family=DM+Sans:wght@400;500;700&family=Space+Mono:wght@400;700&family=Syne:wght@400;500;600;700;800&display=swap" rel="stylesheet" />
11
+ </head>
12
+ <body>
13
+ <script>
14
+ (function() {
15
+ var t = localStorage.getItem('crmy_theme');
16
+ if (t === 'light') document.documentElement.classList.remove('dark');
17
+ else document.documentElement.classList.add('dark');
18
+ })();
19
+ </script>
20
+ <div id="root"></div>
21
+ <script type="module" src="./src/main.tsx"></script>
22
+ </body>
23
+ </html>
package/package.json ADDED
@@ -0,0 +1,76 @@
1
+ {
2
+ "name": "@crmy/web",
3
+ "version": "0.5.1",
4
+ "private": false,
5
+ "type": "module",
6
+ "license": "Apache-2.0",
7
+ "scripts": {
8
+ "dev": "vite",
9
+ "build": "tsc -b && vite build",
10
+ "preview": "vite preview"
11
+ },
12
+ "dependencies": {
13
+ "@hookform/resolvers": "^3.10.0",
14
+ "@radix-ui/react-accordion": "^1.2.11",
15
+ "@radix-ui/react-alert-dialog": "^1.1.14",
16
+ "@radix-ui/react-aspect-ratio": "^1.1.7",
17
+ "@radix-ui/react-avatar": "^1.1.10",
18
+ "@radix-ui/react-checkbox": "^1.3.2",
19
+ "@radix-ui/react-collapsible": "^1.1.11",
20
+ "@radix-ui/react-context-menu": "^2.2.15",
21
+ "@radix-ui/react-dialog": "^1.1.14",
22
+ "@radix-ui/react-dropdown-menu": "^2.1.15",
23
+ "@radix-ui/react-hover-card": "^1.1.14",
24
+ "@radix-ui/react-label": "^2.1.7",
25
+ "@radix-ui/react-menubar": "^1.1.15",
26
+ "@radix-ui/react-navigation-menu": "^1.2.13",
27
+ "@radix-ui/react-popover": "^1.1.14",
28
+ "@radix-ui/react-progress": "^1.1.7",
29
+ "@radix-ui/react-radio-group": "^1.3.7",
30
+ "@radix-ui/react-scroll-area": "^1.2.9",
31
+ "@radix-ui/react-select": "^2.2.5",
32
+ "@radix-ui/react-separator": "^1.1.7",
33
+ "@radix-ui/react-slider": "^1.3.5",
34
+ "@radix-ui/react-slot": "^1.2.3",
35
+ "@radix-ui/react-switch": "^1.2.5",
36
+ "@radix-ui/react-tabs": "^1.1.12",
37
+ "@radix-ui/react-toast": "^1.2.14",
38
+ "@radix-ui/react-toggle": "^1.1.9",
39
+ "@radix-ui/react-toggle-group": "^1.1.10",
40
+ "@radix-ui/react-tooltip": "^1.2.7",
41
+ "@tanstack/react-query": "^5.83.0",
42
+ "class-variance-authority": "^0.7.1",
43
+ "clsx": "^2.1.1",
44
+ "cmdk": "^1.1.1",
45
+ "date-fns": "^3.6.0",
46
+ "embla-carousel-react": "^8.6.0",
47
+ "framer-motion": "^12.36.0",
48
+ "input-otp": "^1.4.2",
49
+ "lucide-react": "^0.468.0",
50
+ "react": "^18.3.1",
51
+ "react-day-picker": "^8.10.1",
52
+ "react-dom": "^18.3.1",
53
+ "react-hook-form": "^7.61.1",
54
+ "react-markdown": "^10.1.0",
55
+ "react-resizable-panels": "^2.1.9",
56
+ "react-router-dom": "^6.30.1",
57
+ "recharts": "^2.15.4",
58
+ "sonner": "^1.7.4",
59
+ "tailwind-merge": "^2.6.0",
60
+ "tailwindcss-animate": "^1.0.7",
61
+ "vaul": "^0.9.9",
62
+ "zod": "^3.25.76",
63
+ "zustand": "^5.0.0"
64
+ },
65
+ "devDependencies": {
66
+ "@types/node": "^22.0.0",
67
+ "@types/react": "^18.3.12",
68
+ "@types/react-dom": "^18.3.1",
69
+ "@vitejs/plugin-react": "^4.3.4",
70
+ "autoprefixer": "^10.4.20",
71
+ "postcss": "^8.4.49",
72
+ "tailwindcss": "^3.4.16",
73
+ "typescript": "^5.6.3",
74
+ "vite": "^6.0.3"
75
+ }
76
+ }
@@ -0,0 +1,6 @@
1
+ export default {
2
+ plugins: {
3
+ tailwindcss: {},
4
+ autoprefixer: {},
5
+ },
6
+ };
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1,13 @@
1
+ <?xml version="1.0" standalone="no"?>
2
+ <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
3
+ "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
4
+ <svg version="1.0" xmlns="http://www.w3.org/2000/svg"
5
+ width="16.000000pt" height="16.000000pt" viewBox="0 0 16.000000 16.000000"
6
+ preserveAspectRatio="xMidYMid meet">
7
+ <metadata>
8
+ Created by potrace 1.16, written by Peter Selinger 2001-2019
9
+ </metadata>
10
+ <g transform="translate(0.000000,16.000000) scale(0.100000,-0.100000)"
11
+ fill="#000000" stroke="none">
12
+ </g>
13
+ </svg>
@@ -0,0 +1 @@
1
+ {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"}
package/src/App.tsx ADDED
@@ -0,0 +1,158 @@
1
+ // Copyright 2026 CRMy Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { useEffect } from 'react';
5
+ import { BrowserRouter, Route, Routes, useLocation, Navigate } from 'react-router-dom';
6
+ import { Toaster as Sonner } from '@/components/ui/sonner';
7
+ import { Toaster } from '@/components/ui/toaster';
8
+ import { TooltipProvider } from '@/components/ui/tooltip';
9
+ import { motion, AnimatePresence } from 'framer-motion';
10
+
11
+ import { Sidebar } from '@/components/layout/Sidebar';
12
+ import { MobileNav } from '@/components/layout/MobileNav';
13
+ import { CommandPalette } from '@/components/crm/CommandPalette';
14
+ import { ShortcutsOverlay } from '@/components/crm/ShortcutsOverlay';
15
+ import { DrawerShell } from '@/components/crm/DrawerShell';
16
+ import { QuickAddDrawer } from '@/components/crm/QuickAddDrawer';
17
+ import { ContactDrawer } from '@/components/crm/ContactDrawer';
18
+ import { OpportunityDrawer } from '@/components/crm/OpportunityDrawer';
19
+ import { UseCaseDrawer } from '@/components/crm/UseCaseDrawer';
20
+ import { AccountDrawer } from '@/components/crm/AccountDrawer';
21
+ import { AssignmentDrawer } from '@/components/crm/AssignmentDrawer';
22
+ import { useKeyboardShortcuts } from '@/hooks/useKeyboardShortcuts';
23
+ import { useAppStore } from '@/store/appStore';
24
+ import { useTheme } from '@/hooks/useTheme';
25
+ import { AgentSettingsProvider } from '@/contexts/AgentSettingsContext';
26
+ import { AIFab } from '@/components/crm/AIFab';
27
+
28
+ import { LoginPage } from '@/pages/auth/Login';
29
+ import Dashboard from '@/pages/Dashboard';
30
+ import Contacts from '@/pages/Contacts';
31
+ import Accounts from '@/pages/Accounts';
32
+ import Opportunities from '@/pages/Opportunities';
33
+ import UseCasesPage from '@/pages/UseCases';
34
+ import Activities from '@/pages/Activities';
35
+ import Agent from '@/pages/Agent';
36
+ import Settings from '@/pages/Settings';
37
+ import NotFound from '@/pages/NotFound';
38
+ import { HITLPage } from '@/pages/hitl/HITL';
39
+ import AssignmentsPage from '@/pages/Assignments';
40
+ import InboxPage from '@/pages/Inbox';
41
+
42
+ function ThemeApplier() {
43
+ const { darkVariant } = useAppStore();
44
+ useTheme(); // applies theme class to html element
45
+
46
+ useEffect(() => {
47
+ const html = document.documentElement;
48
+ if (darkVariant === 'charcoal') {
49
+ html.classList.add('charcoal');
50
+ } else {
51
+ html.classList.remove('charcoal');
52
+ }
53
+ }, [darkVariant]);
54
+
55
+ return null;
56
+ }
57
+
58
+ function AuthGuard({ children }: { children: React.ReactNode }) {
59
+ const token = localStorage.getItem('crmy_token');
60
+ if (!token) {
61
+ return <Navigate to="/login" replace />;
62
+ }
63
+ return <>{children}</>;
64
+ }
65
+
66
+ function AnimatedRoutes() {
67
+ const location = useLocation();
68
+ return (
69
+ <AnimatePresence mode="wait">
70
+ <motion.div
71
+ key={location.pathname}
72
+ initial={{ opacity: 0, y: 8 }}
73
+ animate={{ opacity: 1, y: 0 }}
74
+ exit={{ opacity: 0, y: -4 }}
75
+ transition={{ duration: 0.15, ease: 'easeOut' }}
76
+ className="flex-1 flex flex-col overflow-hidden"
77
+ >
78
+ <Routes location={location}>
79
+ <Route path="/" element={<Dashboard />} />
80
+ <Route path="/contacts" element={<Contacts />} />
81
+ <Route path="/accounts" element={<Accounts />} />
82
+ <Route path="/opportunities" element={<Opportunities />} />
83
+ <Route path="/use-cases" element={<UseCasesPage />} />
84
+ <Route path="/activities" element={<Activities />} />
85
+ <Route path="/assignments" element={<InboxPage />} />
86
+ <Route path="/inbox" element={<Navigate to="/assignments" replace />} />
87
+ <Route path="/hitl" element={<Navigate to="/assignments" replace />} />
88
+ <Route path="/agent" element={<Agent />} />
89
+ <Route path="/settings/*" element={<Settings />} />
90
+ <Route path="*" element={<NotFound />} />
91
+ </Routes>
92
+ </motion.div>
93
+ </AnimatePresence>
94
+ );
95
+ }
96
+
97
+ function AppContent() {
98
+ useKeyboardShortcuts();
99
+ const { drawerType, zenMode } = useAppStore();
100
+
101
+ const drawerTitle = drawerType === 'contact' ? 'Contact Details'
102
+ : drawerType === 'opportunity' ? 'Opportunity Details'
103
+ : drawerType === 'use-case' ? 'Use Case Details'
104
+ : drawerType === 'account' ? 'Account Details'
105
+ : drawerType === 'assignment' ? 'Assignment Details'
106
+ : '';
107
+
108
+ return (
109
+ <div className="flex h-screen w-full overflow-hidden">
110
+ {!zenMode && <Sidebar />}
111
+ <main className="flex-1 flex flex-col overflow-hidden">
112
+ <div className="flex-1 flex flex-col overflow-hidden">
113
+ <AnimatedRoutes />
114
+ </div>
115
+ </main>
116
+ {!zenMode && <MobileNav />}
117
+ {/* Drawers */}
118
+ <DrawerShell title={drawerTitle}>
119
+ {drawerType === 'contact' && <ContactDrawer />}
120
+ {drawerType === 'opportunity' && <OpportunityDrawer />}
121
+ {drawerType === 'use-case' && <UseCaseDrawer />}
122
+ {drawerType === 'account' && <AccountDrawer />}
123
+ {drawerType === 'assignment' && <AssignmentDrawer />}
124
+ </DrawerShell>
125
+
126
+ {/* Overlays */}
127
+ <CommandPalette />
128
+ <ShortcutsOverlay />
129
+ <QuickAddDrawer />
130
+ <AIFab />
131
+ </div>
132
+ );
133
+ }
134
+
135
+ export function App() {
136
+ return (
137
+ <AgentSettingsProvider>
138
+ <TooltipProvider>
139
+ <Toaster />
140
+ <Sonner />
141
+ <BrowserRouter basename="/app">
142
+ <ThemeApplier />
143
+ <Routes>
144
+ <Route path="/login" element={<LoginPage />} />
145
+ <Route
146
+ path="/*"
147
+ element={
148
+ <AuthGuard>
149
+ <AppContent />
150
+ </AuthGuard>
151
+ }
152
+ />
153
+ </Routes>
154
+ </BrowserRouter>
155
+ </TooltipProvider>
156
+ </AgentSettingsProvider>
157
+ );
158
+ }
@@ -0,0 +1,82 @@
1
+ // Copyright 2026 CRMy Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ const BASE = '/api/v1';
5
+
6
+ function getToken(): string | null {
7
+ return localStorage.getItem('crmy_token');
8
+ }
9
+
10
+ export function setToken(token: string) {
11
+ localStorage.setItem('crmy_token', token);
12
+ }
13
+
14
+ export function clearToken() {
15
+ localStorage.removeItem('crmy_token');
16
+ localStorage.removeItem('crmy_user');
17
+ }
18
+
19
+ export function getUser(): { id: string; email: string; name: string; role: string; tenant_id: string } | null {
20
+ const raw = localStorage.getItem('crmy_user');
21
+ return raw ? JSON.parse(raw) : null;
22
+ }
23
+
24
+ export function setUser(user: { id: string; email: string; name: string; role: string; tenant_id: string }) {
25
+ localStorage.setItem('crmy_user', JSON.stringify(user));
26
+ }
27
+
28
+ async function request<T>(path: string, opts: RequestInit = {}): Promise<T> {
29
+ const token = getToken();
30
+ const headers: Record<string, string> = {
31
+ 'Content-Type': 'application/json',
32
+ ...(opts.headers as Record<string, string>),
33
+ };
34
+ if (token) headers['Authorization'] = `Bearer ${token}`;
35
+
36
+ let res: Response;
37
+ try {
38
+ res = await fetch(path.startsWith('/') ? path : `${BASE}/${path}`, {
39
+ ...opts,
40
+ headers,
41
+ });
42
+ } catch {
43
+ throw new Error('Unable to reach the server. Check your connection and try again.');
44
+ }
45
+
46
+ if (res.status === 401) {
47
+ clearToken();
48
+ window.location.href = '/app/login';
49
+ throw new Error('Unauthorized');
50
+ }
51
+
52
+ if (!res.ok) {
53
+ const body = await res.json().catch(() => ({ detail: res.statusText }));
54
+ throw new Error(body.detail || `HTTP ${res.status}`);
55
+ }
56
+
57
+ if (res.status === 204) return undefined as T;
58
+ return res.json();
59
+ }
60
+
61
+ export const api = {
62
+ get: <T>(path: string) => request<T>(path),
63
+ post: <T>(path: string, body?: unknown) =>
64
+ request<T>(path, { method: 'POST', body: body ? JSON.stringify(body) : undefined }),
65
+ patch: <T>(path: string, body: unknown) =>
66
+ request<T>(path, { method: 'PATCH', body: JSON.stringify(body) }),
67
+ delete: <T>(path: string) => request<T>(path, { method: 'DELETE' }),
68
+ };
69
+
70
+ // Auth endpoints (no /api/v1 prefix)
71
+ export const auth = {
72
+ login: (email: string, password: string) =>
73
+ request<{ token: string; user: { id: string; email: string; name: string; role: string; tenant_id: string } }>(
74
+ '/auth/login',
75
+ { method: 'POST', body: JSON.stringify({ email, password }) },
76
+ ),
77
+ register: (data: { email: string; password: string; name: string; tenant_name: string }) =>
78
+ request<{ token: string; user: { id: string; email: string; name: string; role: string; tenant_id: string } }>(
79
+ '/auth/register',
80
+ { method: 'POST', body: JSON.stringify(data) },
81
+ ),
82
+ };