@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
@@ -0,0 +1,101 @@
1
+ // Copyright 2026 CRMy Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { useState } from 'react';
5
+ import { ShieldCheck } from 'lucide-react';
6
+ import { Card, CardContent } from '../../components/ui/card';
7
+ import { Badge } from '../../components/ui/badge';
8
+ import { Button } from '../../components/ui/button';
9
+ import { Input } from '../../components/ui/input';
10
+ import { useHITLRequests, useResolveHITL } from '../../api/hooks';
11
+
12
+ function timeAgo(date: string) {
13
+ const diff = Date.now() - new Date(date).getTime();
14
+ const mins = Math.floor(diff / 60000);
15
+ if (mins < 60) return `${mins}m ago`;
16
+ const hours = Math.floor(mins / 60);
17
+ if (hours < 24) return `${hours}h ago`;
18
+ return `${Math.floor(hours / 24)}d ago`;
19
+ }
20
+
21
+ export function HITLPage() {
22
+ const { data, isLoading } = useHITLRequests();
23
+ const resolve = useResolveHITL();
24
+ const [notes, setNotes] = useState<Record<string, string>>({});
25
+ const [expanded, setExpanded] = useState<Record<string, boolean>>({});
26
+
27
+ const requests = (data as any)?.data ?? [];
28
+
29
+ return (
30
+ <div className="space-y-4">
31
+ <h1 className="font-display text-2xl font-bold">HITL Queue</h1>
32
+ {isLoading ? (
33
+ <p className="text-muted-foreground">Loading...</p>
34
+ ) : requests.length === 0 ? (
35
+ <Card>
36
+ <CardContent className="flex flex-col items-center justify-center py-12 text-center">
37
+ <ShieldCheck className="h-12 w-12 text-emerald-500 mb-3" />
38
+ <p className="text-lg font-medium">No pending approvals</p>
39
+ <p className="text-sm text-muted-foreground">Your agents are running autonomously</p>
40
+ </CardContent>
41
+ </Card>
42
+ ) : (
43
+ <div className="space-y-4">
44
+ {requests.map((r: any) => (
45
+ <Card key={r.id}>
46
+ <CardContent className="p-4 space-y-3">
47
+ <div className="flex items-center gap-3">
48
+ <Badge variant="outline">{r.action_type}</Badge>
49
+ <span className="text-sm text-muted-foreground">Submitted by: {r.agent_id ?? r.created_by ?? 'agent'}</span>
50
+ <span className="text-sm text-muted-foreground">{timeAgo(r.created_at)}</span>
51
+ {r.expires_at && (
52
+ <span className="ml-auto text-xs text-muted-foreground">
53
+ Expires: {new Date(r.expires_at).toLocaleString()}
54
+ </span>
55
+ )}
56
+ </div>
57
+ <p className="text-sm">{r.action_summary}</p>
58
+ <div>
59
+ <button
60
+ className="text-xs text-primary hover:underline"
61
+ onClick={() => setExpanded({ ...expanded, [r.id]: !expanded[r.id] })}
62
+ >
63
+ {expanded[r.id] ? 'Hide payload' : 'Show payload'}
64
+ </button>
65
+ {expanded[r.id] && (
66
+ <pre className="mt-2 max-h-48 overflow-auto rounded bg-muted p-3 text-xs">
67
+ {JSON.stringify(r.action_payload, null, 2)}
68
+ </pre>
69
+ )}
70
+ </div>
71
+ <div className="flex items-center gap-2">
72
+ <Input
73
+ placeholder="Note (optional)"
74
+ value={notes[r.id] ?? ''}
75
+ onChange={(e) => setNotes({ ...notes, [r.id]: e.target.value })}
76
+ className="flex-1"
77
+ />
78
+ <Button
79
+ variant="destructive"
80
+ size="sm"
81
+ onClick={() => resolve.mutate({ id: r.id, status: 'rejected', note: notes[r.id] })}
82
+ disabled={resolve.isPending}
83
+ >
84
+ Reject
85
+ </Button>
86
+ <Button
87
+ size="sm"
88
+ onClick={() => resolve.mutate({ id: r.id, status: 'approved', note: notes[r.id] })}
89
+ disabled={resolve.isPending}
90
+ >
91
+ Approve
92
+ </Button>
93
+ </div>
94
+ </CardContent>
95
+ </Card>
96
+ ))}
97
+ </div>
98
+ )}
99
+ </div>
100
+ );
101
+ }
@@ -0,0 +1,103 @@
1
+ import { create } from 'zustand';
2
+ import { persist } from 'zustand/middleware';
3
+
4
+ type DrawerType = 'contact' | 'opportunity' | 'use-case' | 'account' | 'assignment' | null;
5
+
6
+ export interface AIContextEntity {
7
+ type: 'contact' | 'opportunity' | 'use-case' | 'account';
8
+ id: string;
9
+ name: string;
10
+ detail?: string;
11
+ }
12
+ type QuickAddType = 'contact' | 'opportunity' | 'use-case' | 'activity' | 'account' | 'assignment' | null;
13
+
14
+ interface AppState {
15
+ darkVariant: 'warm' | 'charcoal';
16
+ setDarkVariant: (variant: 'warm' | 'charcoal') => void;
17
+ sidebarExpanded: boolean;
18
+ setSidebarExpanded: (expanded: boolean) => void;
19
+ toggleSidebar: () => void;
20
+
21
+ drawerOpen: boolean;
22
+ drawerType: DrawerType;
23
+ drawerEntityId: string | null;
24
+ openDrawer: (type: DrawerType, entityId?: string) => void;
25
+ closeDrawer: () => void;
26
+
27
+ commandPaletteOpen: boolean;
28
+ setCommandPaletteOpen: (open: boolean) => void;
29
+
30
+ shortcutsOpen: boolean;
31
+ setShortcutsOpen: (open: boolean) => void;
32
+
33
+ zenMode: boolean;
34
+ toggleZenMode: () => void;
35
+
36
+ theme: 'dark' | 'light' | 'system';
37
+ setTheme: (theme: 'dark' | 'light' | 'system') => void;
38
+
39
+ quickAddOpen: boolean;
40
+ quickAddType: QuickAddType;
41
+ openQuickAdd: (type: QuickAddType) => void;
42
+ closeQuickAdd: () => void;
43
+
44
+ aiContext: AIContextEntity | null;
45
+ openAIWithContext: (context: AIContextEntity) => void;
46
+ }
47
+
48
+ export const useAppStore = create<AppState>()(
49
+ persist(
50
+ (set) => ({
51
+ sidebarExpanded: false,
52
+ setSidebarExpanded: (expanded) => set({ sidebarExpanded: expanded }),
53
+ toggleSidebar: () => set((s) => ({ sidebarExpanded: !s.sidebarExpanded })),
54
+
55
+ drawerOpen: false,
56
+ drawerType: null,
57
+ drawerEntityId: null,
58
+ openDrawer: (type, entityId) => set({ drawerOpen: true, drawerType: type, drawerEntityId: entityId ?? null }),
59
+ closeDrawer: () => set({ drawerOpen: false, drawerType: null, drawerEntityId: null }),
60
+
61
+ commandPaletteOpen: false,
62
+ setCommandPaletteOpen: (open) => set({ commandPaletteOpen: open }),
63
+
64
+ shortcutsOpen: false,
65
+ setShortcutsOpen: (open) => set({ shortcutsOpen: open }),
66
+
67
+ zenMode: false,
68
+ toggleZenMode: () => set((s) => ({ zenMode: !s.zenMode })),
69
+
70
+ theme: 'dark',
71
+ setTheme: (theme) => {
72
+ const root = document.documentElement;
73
+ root.classList.remove('dark', 'light');
74
+ if (theme === 'system') {
75
+ const sys = window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
76
+ root.classList.add(sys);
77
+ } else {
78
+ root.classList.add(theme);
79
+ }
80
+ set({ theme });
81
+ },
82
+
83
+ quickAddOpen: false,
84
+ quickAddType: null,
85
+ openQuickAdd: (type) => set({ quickAddOpen: true, quickAddType: type }),
86
+ closeQuickAdd: () => set({ quickAddOpen: false, quickAddType: null }),
87
+
88
+ aiContext: null,
89
+ openAIWithContext: (context) => set({ aiContext: context }),
90
+
91
+ darkVariant: 'warm',
92
+ setDarkVariant: (variant) => set({ darkVariant: variant }),
93
+ }),
94
+ {
95
+ name: 'crmy-app-store',
96
+ partialize: (state) => ({
97
+ sidebarExpanded: state.sidebarExpanded,
98
+ theme: state.theme,
99
+ darkVariant: state.darkVariant,
100
+ }),
101
+ }
102
+ )
103
+ );
@@ -0,0 +1,14 @@
1
+ /// <reference types="vite/client" />
2
+
3
+ declare module '*.png' {
4
+ const src: string;
5
+ export default src;
6
+ }
7
+ declare module '*.jpg' {
8
+ const src: string;
9
+ export default src;
10
+ }
11
+ declare module '*.svg' {
12
+ const src: string;
13
+ export default src;
14
+ }
@@ -0,0 +1,121 @@
1
+ /** @type {import('tailwindcss').Config} */
2
+ export default {
3
+ darkMode: ["class"],
4
+ content: ["./pages/**/*.{ts,tsx}", "./components/**/*.{ts,tsx}", "./app/**/*.{ts,tsx}", "./src/**/*.{ts,tsx}"],
5
+ prefix: "",
6
+ theme: {
7
+ container: {
8
+ center: true,
9
+ padding: "2rem",
10
+ screens: { "2xl": "1400px" },
11
+ },
12
+ extend: {
13
+ fontFamily: {
14
+ display: ["Plus Jakarta Sans", "sans-serif"],
15
+ brand: ["Syne", "sans-serif"],
16
+ body: ["DM Sans", "sans-serif"],
17
+ mono: ["Space Mono", "monospace"],
18
+ },
19
+ colors: {
20
+ border: "hsl(var(--border))",
21
+ input: "hsl(var(--input))",
22
+ ring: "hsl(var(--ring))",
23
+ background: "hsl(var(--background))",
24
+ foreground: "hsl(var(--foreground))",
25
+ primary: {
26
+ DEFAULT: "hsl(var(--primary))",
27
+ foreground: "hsl(var(--primary-foreground))",
28
+ },
29
+ secondary: {
30
+ DEFAULT: "hsl(var(--secondary))",
31
+ foreground: "hsl(var(--secondary-foreground))",
32
+ },
33
+ destructive: {
34
+ DEFAULT: "hsl(var(--destructive))",
35
+ foreground: "hsl(var(--destructive-foreground))",
36
+ },
37
+ muted: {
38
+ DEFAULT: "hsl(var(--muted))",
39
+ foreground: "hsl(var(--muted-foreground))",
40
+ },
41
+ accent: {
42
+ DEFAULT: "hsl(var(--accent))",
43
+ foreground: "hsl(var(--accent-foreground))",
44
+ },
45
+ popover: {
46
+ DEFAULT: "hsl(var(--popover))",
47
+ foreground: "hsl(var(--popover-foreground))",
48
+ },
49
+ card: {
50
+ DEFAULT: "hsl(var(--card))",
51
+ foreground: "hsl(var(--card-foreground))",
52
+ },
53
+ success: {
54
+ DEFAULT: "hsl(var(--success))",
55
+ foreground: "hsl(var(--success-foreground))",
56
+ },
57
+ warning: {
58
+ DEFAULT: "hsl(var(--warning))",
59
+ foreground: "hsl(var(--warning-foreground))",
60
+ },
61
+ info: {
62
+ DEFAULT: "hsl(var(--info))",
63
+ foreground: "hsl(var(--info-foreground))",
64
+ },
65
+ surface: {
66
+ DEFAULT: "hsl(var(--surface))",
67
+ raised: "hsl(var(--surface-raised))",
68
+ sunken: "hsl(var(--surface-sunken))",
69
+ },
70
+ sidebar: {
71
+ DEFAULT: "hsl(var(--sidebar-background))",
72
+ foreground: "hsl(var(--sidebar-foreground))",
73
+ primary: "hsl(var(--sidebar-primary))",
74
+ "primary-foreground": "hsl(var(--sidebar-primary-foreground))",
75
+ accent: "hsl(var(--sidebar-accent))",
76
+ "accent-foreground": "hsl(var(--sidebar-accent-foreground))",
77
+ border: "hsl(var(--sidebar-border))",
78
+ ring: "hsl(var(--sidebar-ring))",
79
+ },
80
+ },
81
+ borderRadius: {
82
+ lg: "var(--radius)",
83
+ md: "calc(var(--radius) - 2px)",
84
+ sm: "calc(var(--radius) - 4px)",
85
+ xl: "1rem",
86
+ "2xl": "1.25rem",
87
+ "3xl": "1.5rem",
88
+ },
89
+ keyframes: {
90
+ "accordion-down": {
91
+ from: { height: "0" },
92
+ to: { height: "var(--radix-accordion-content-height)" },
93
+ },
94
+ "accordion-up": {
95
+ from: { height: "var(--radix-accordion-content-height)" },
96
+ to: { height: "0" },
97
+ },
98
+ "slide-in-right": {
99
+ from: { transform: "translateX(100%)" },
100
+ to: { transform: "translateX(0)" },
101
+ },
102
+ "slide-out-right": {
103
+ from: { transform: "translateX(0)" },
104
+ to: { transform: "translateX(100%)" },
105
+ },
106
+ "slide-up": {
107
+ from: { transform: "translateY(100%)" },
108
+ to: { transform: "translateY(0)" },
109
+ },
110
+ },
111
+ animation: {
112
+ "accordion-down": "accordion-down 0.2s ease-out",
113
+ "accordion-up": "accordion-up 0.2s ease-out",
114
+ "slide-in-right": "slide-in-right 0.2s ease-out",
115
+ "slide-out-right": "slide-out-right 0.2s ease-out",
116
+ "slide-up": "slide-up 0.3s ease-out",
117
+ },
118
+ },
119
+ },
120
+ plugins: [require("tailwindcss-animate")],
121
+ };
package/tsconfig.json ADDED
@@ -0,0 +1,24 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2020",
4
+ "useDefineForClassFields": true,
5
+ "lib": ["ES2020", "DOM", "DOM.Iterable"],
6
+ "module": "ESNext",
7
+ "skipLibCheck": true,
8
+ "moduleResolution": "bundler",
9
+ "allowImportingTsExtensions": true,
10
+ "isolatedModules": true,
11
+ "moduleDetection": "force",
12
+ "noEmit": true,
13
+ "jsx": "react-jsx",
14
+ "strict": true,
15
+ "noUnusedLocals": false,
16
+ "noUnusedParameters": false,
17
+ "noFallthroughCasesInSwitch": true,
18
+ "paths": {
19
+ "@/*": ["./src/*"]
20
+ },
21
+ "baseUrl": "."
22
+ },
23
+ "include": ["src"]
24
+ }
package/vite.config.ts ADDED
@@ -0,0 +1,27 @@
1
+ // Copyright 2026 CRMy Contributors
2
+ // SPDX-License-Identifier: Apache-2.0
3
+
4
+ import { defineConfig } from 'vite';
5
+ import react from '@vitejs/plugin-react';
6
+ import path from 'path';
7
+
8
+ export default defineConfig({
9
+ plugins: [react()],
10
+ base: '/app/',
11
+ resolve: {
12
+ alias: {
13
+ '@': path.resolve(__dirname, './src'),
14
+ },
15
+ },
16
+ server: {
17
+ port: 5173,
18
+ proxy: {
19
+ '/api': 'http://localhost:3001',
20
+ '/auth': 'http://localhost:3001',
21
+ '/health': 'http://localhost:3001',
22
+ },
23
+ },
24
+ build: {
25
+ outDir: 'dist',
26
+ },
27
+ });