@agent-relay/dashboard 2.0.80 → 2.0.82

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 (244) hide show
  1. package/out/404.html +1 -1
  2. package/out/_next/static/chunks/{118-4c8241b0218335de.js → 118-ae2b650136a5a5fc.js} +1 -1
  3. package/out/_next/static/chunks/407-0c82986cf79c8ecb.js +1 -0
  4. package/out/_next/static/chunks/app/app/[[...slug]]/{page-1e81c047cff17212.js → page-f7eca1b66fb4249b.js} +1 -1
  5. package/out/_next/static/chunks/app/{page-6892fe2dd07fb48b.js → page-0ee604f7070d14c0.js} +1 -1
  6. package/out/_next/static/css/8968d98ed4c4d33f.css +1 -0
  7. package/out/about.html +2 -2
  8. package/out/about.txt +1 -1
  9. package/out/app/onboarding.html +1 -1
  10. package/out/app/onboarding.txt +1 -1
  11. package/out/app.html +1 -1
  12. package/out/app.txt +2 -2
  13. package/out/blog/go-to-bed-wake-up-to-a-finished-product.html +2 -2
  14. package/out/blog/go-to-bed-wake-up-to-a-finished-product.txt +1 -1
  15. package/out/blog/let-them-cook-multi-agent-orchestration.html +2 -2
  16. package/out/blog/let-them-cook-multi-agent-orchestration.txt +2 -2
  17. package/out/blog.html +2 -2
  18. package/out/blog.txt +1 -1
  19. package/out/careers.html +2 -2
  20. package/out/careers.txt +1 -1
  21. package/out/changelog.html +2 -2
  22. package/out/changelog.txt +1 -1
  23. package/out/cloud/link.html +1 -1
  24. package/out/cloud/link.txt +2 -2
  25. package/out/complete-profile.html +2 -2
  26. package/out/complete-profile.txt +1 -1
  27. package/out/connect-repos.html +1 -1
  28. package/out/connect-repos.txt +1 -1
  29. package/out/contact.html +2 -2
  30. package/out/contact.txt +1 -1
  31. package/out/docs.html +2 -2
  32. package/out/docs.txt +1 -1
  33. package/out/history.html +1 -1
  34. package/out/history.txt +2 -2
  35. package/out/index.html +1 -1
  36. package/out/index.txt +2 -2
  37. package/out/login.html +2 -2
  38. package/out/login.txt +1 -1
  39. package/out/metrics.html +1 -1
  40. package/out/metrics.txt +2 -2
  41. package/out/pricing.html +2 -2
  42. package/out/pricing.txt +1 -1
  43. package/out/privacy.html +2 -2
  44. package/out/privacy.txt +1 -1
  45. package/out/providers/setup/claude.html +1 -1
  46. package/out/providers/setup/claude.txt +1 -1
  47. package/out/providers/setup/codex.html +1 -1
  48. package/out/providers/setup/codex.txt +1 -1
  49. package/out/providers/setup/cursor.html +1 -1
  50. package/out/providers/setup/cursor.txt +1 -1
  51. package/out/providers.html +1 -1
  52. package/out/providers.txt +1 -1
  53. package/out/security.html +2 -2
  54. package/out/security.txt +1 -1
  55. package/out/signup.html +2 -2
  56. package/out/signup.txt +1 -1
  57. package/out/terms.html +2 -2
  58. package/out/terms.txt +1 -1
  59. package/package.json +7 -1
  60. package/src/app/about/page.tsx +7 -0
  61. package/src/app/app/[[...slug]]/DashboardPageClient.tsx +853 -0
  62. package/src/app/app/[[...slug]]/page.tsx +23 -0
  63. package/src/app/app/onboarding/page.tsx +394 -0
  64. package/src/app/apple-icon.png +0 -0
  65. package/src/app/blog/go-to-bed-wake-up-to-a-finished-product/page.tsx +88 -0
  66. package/src/app/blog/let-them-cook-multi-agent-orchestration/page.tsx +93 -0
  67. package/src/app/blog/page.tsx +15 -0
  68. package/src/app/careers/page.tsx +7 -0
  69. package/src/app/changelog/page.tsx +7 -0
  70. package/src/app/cloud/link/page.tsx +464 -0
  71. package/src/app/complete-profile/page.tsx +204 -0
  72. package/src/app/connect-repos/page.tsx +410 -0
  73. package/src/app/contact/page.tsx +7 -0
  74. package/src/app/docs/page.tsx +7 -0
  75. package/src/app/favicon.png +0 -0
  76. package/src/app/globals.css +200 -0
  77. package/src/app/history/page.tsx +658 -0
  78. package/src/app/layout.tsx +25 -0
  79. package/src/app/login/page.tsx +424 -0
  80. package/src/app/metrics/page.tsx +781 -0
  81. package/src/app/page.tsx +59 -0
  82. package/src/app/pricing/page.tsx +7 -0
  83. package/src/app/privacy/page.tsx +7 -0
  84. package/src/app/providers/page.tsx +193 -0
  85. package/src/app/providers/setup/[provider]/ProviderSetupClient.tsx +197 -0
  86. package/src/app/providers/setup/[provider]/constants.ts +35 -0
  87. package/src/app/providers/setup/[provider]/page.tsx +42 -0
  88. package/src/app/security/page.tsx +7 -0
  89. package/src/app/signup/page.tsx +533 -0
  90. package/src/app/terms/page.tsx +7 -0
  91. package/src/components/ActivityFeed.tsx +216 -0
  92. package/src/components/AddWorkspaceModal.tsx +170 -0
  93. package/src/components/AgentCard.test.tsx +134 -0
  94. package/src/components/AgentCard.tsx +585 -0
  95. package/src/components/AgentList.test.tsx +147 -0
  96. package/src/components/AgentList.tsx +419 -0
  97. package/src/components/AgentLogPreview.tsx +173 -0
  98. package/src/components/AgentProfilePanel.tsx +569 -0
  99. package/src/components/App.tsx +3424 -0
  100. package/src/components/BillingPanel.tsx +922 -0
  101. package/src/components/BillingResult.tsx +447 -0
  102. package/src/components/BroadcastComposer.tsx +690 -0
  103. package/src/components/ChannelAdminPanel.tsx +773 -0
  104. package/src/components/ChannelBrowser.tsx +385 -0
  105. package/src/components/ChannelChat.tsx +261 -0
  106. package/src/components/ChannelSidebar.tsx +399 -0
  107. package/src/components/CloudSessionProvider.tsx +130 -0
  108. package/src/components/CommandPalette.tsx +815 -0
  109. package/src/components/ConfirmationDialog.tsx +133 -0
  110. package/src/components/ConversationHistory.tsx +518 -0
  111. package/src/components/CoordinatorPanel.tsx +956 -0
  112. package/src/components/DecisionQueue.tsx +717 -0
  113. package/src/components/DirectMessageView.tsx +164 -0
  114. package/src/components/FileAutocomplete.tsx +368 -0
  115. package/src/components/FleetOverview.tsx +278 -0
  116. package/src/components/LogViewer.tsx +310 -0
  117. package/src/components/LogViewerPanel.tsx +482 -0
  118. package/src/components/Logo.tsx +284 -0
  119. package/src/components/MentionAutocomplete.tsx +384 -0
  120. package/src/components/MessageComposer.tsx +473 -0
  121. package/src/components/MessageList.tsx +725 -0
  122. package/src/components/MessageSenderName.tsx +91 -0
  123. package/src/components/MessageStatusIndicator.tsx +142 -0
  124. package/src/components/NewConversationModal.tsx +400 -0
  125. package/src/components/NotificationToast.tsx +488 -0
  126. package/src/components/OnlineUsersIndicator.tsx +164 -0
  127. package/src/components/Pagination.tsx +124 -0
  128. package/src/components/PricingPlans.tsx +386 -0
  129. package/src/components/ProjectList.tsx +711 -0
  130. package/src/components/ProviderAuthFlow.tsx +343 -0
  131. package/src/components/ProviderConnectionList.tsx +375 -0
  132. package/src/components/ProvisioningProgress.tsx +730 -0
  133. package/src/components/ReactionChips.tsx +70 -0
  134. package/src/components/ReactionPicker.tsx +121 -0
  135. package/src/components/RepoAccessPanel.tsx +787 -0
  136. package/src/components/RepositoriesPanel.tsx +901 -0
  137. package/src/components/ServerCard.tsx +202 -0
  138. package/src/components/SessionExpiredModal.tsx +128 -0
  139. package/src/components/SpawnModal.test.tsx +190 -0
  140. package/src/components/SpawnModal.tsx +1001 -0
  141. package/src/components/TaskAssignmentUI.tsx +375 -0
  142. package/src/components/TerminalProviderSetup.tsx +517 -0
  143. package/src/components/ThemeProvider.tsx +159 -0
  144. package/src/components/ThinkingIndicator.tsx +231 -0
  145. package/src/components/ThreadList.tsx +198 -0
  146. package/src/components/ThreadPanel.tsx +405 -0
  147. package/src/components/TrajectoryViewer.tsx +698 -0
  148. package/src/components/TypingIndicator.tsx +69 -0
  149. package/src/components/UsageBanner.tsx +231 -0
  150. package/src/components/UserProfilePanel.tsx +233 -0
  151. package/src/components/WorkspaceContext.tsx +95 -0
  152. package/src/components/WorkspaceSelector.tsx +234 -0
  153. package/src/components/WorkspaceStatusIndicator.tsx +396 -0
  154. package/src/components/XTermInteractive.tsx +516 -0
  155. package/src/components/XTermLogViewer.tsx +719 -0
  156. package/src/components/channels/ChannelDialogs.tsx +1411 -0
  157. package/src/components/channels/ChannelHeader.tsx +317 -0
  158. package/src/components/channels/ChannelMessageList.tsx +463 -0
  159. package/src/components/channels/ChannelViewV1.tsx +146 -0
  160. package/src/components/channels/MessageInput.tsx +302 -0
  161. package/src/components/channels/SearchInput.tsx +172 -0
  162. package/src/components/channels/SearchResults.tsx +336 -0
  163. package/src/components/channels/api.test.ts +1527 -0
  164. package/src/components/channels/api.ts +703 -0
  165. package/src/components/channels/index.ts +76 -0
  166. package/src/components/channels/mockApi.ts +344 -0
  167. package/src/components/channels/types.ts +566 -0
  168. package/src/components/hooks/index.ts +58 -0
  169. package/src/components/hooks/useAgentLogs.ts +504 -0
  170. package/src/components/hooks/useAgents.ts +127 -0
  171. package/src/components/hooks/useBroadcastDedup.test.ts +371 -0
  172. package/src/components/hooks/useBroadcastDedup.ts +86 -0
  173. package/src/components/hooks/useChannelAdmin.ts +329 -0
  174. package/src/components/hooks/useChannelBrowser.ts +239 -0
  175. package/src/components/hooks/useChannelCommands.ts +138 -0
  176. package/src/components/hooks/useChannels.ts +367 -0
  177. package/src/components/hooks/useDebounce.ts +29 -0
  178. package/src/components/hooks/useDirectMessage.test.ts +952 -0
  179. package/src/components/hooks/useDirectMessage.ts +141 -0
  180. package/src/components/hooks/useMessages.ts +310 -0
  181. package/src/components/hooks/useOrchestrator.test.ts +165 -0
  182. package/src/components/hooks/useOrchestrator.ts +424 -0
  183. package/src/components/hooks/usePinnedAgents.test.ts +356 -0
  184. package/src/components/hooks/usePinnedAgents.ts +140 -0
  185. package/src/components/hooks/usePresence.test.ts +245 -0
  186. package/src/components/hooks/usePresence.ts +377 -0
  187. package/src/components/hooks/useRecentRepos.ts +130 -0
  188. package/src/components/hooks/useSession.ts +209 -0
  189. package/src/components/hooks/useThread.ts +138 -0
  190. package/src/components/hooks/useTrajectory.ts +265 -0
  191. package/src/components/hooks/useWebSocket.ts +290 -0
  192. package/src/components/hooks/useWorkspaceMembers.ts +132 -0
  193. package/src/components/hooks/useWorkspaceRepos.ts +73 -0
  194. package/src/components/hooks/useWorkspaceStatus.ts +237 -0
  195. package/src/components/index.ts +81 -0
  196. package/src/components/layout/Header.tsx +311 -0
  197. package/src/components/layout/RepoContextHeader.tsx +361 -0
  198. package/src/components/layout/Sidebar.archive.test.tsx +126 -0
  199. package/src/components/layout/Sidebar.test.tsx +691 -0
  200. package/src/components/layout/Sidebar.tsx +900 -0
  201. package/src/components/layout/index.ts +7 -0
  202. package/src/components/settings/BillingSettingsPanel.tsx +564 -0
  203. package/src/components/settings/SettingsPage.tsx +683 -0
  204. package/src/components/settings/TeamSettingsPanel.tsx +560 -0
  205. package/src/components/settings/WorkspaceSettingsPanel.tsx +1368 -0
  206. package/src/components/settings/index.ts +11 -0
  207. package/src/components/settings/types.ts +79 -0
  208. package/src/components/utils/messageFormatting.test.tsx +331 -0
  209. package/src/components/utils/messageFormatting.tsx +597 -0
  210. package/src/index.ts +63 -0
  211. package/src/landing/AboutPage.tsx +77 -0
  212. package/src/landing/BlogContent.tsx +187 -0
  213. package/src/landing/BlogPage.tsx +47 -0
  214. package/src/landing/CareersPage.tsx +53 -0
  215. package/src/landing/ChangelogPage.tsx +33 -0
  216. package/src/landing/ContactPage.tsx +41 -0
  217. package/src/landing/DocsPage.tsx +43 -0
  218. package/src/landing/LandingPage.tsx +702 -0
  219. package/src/landing/PricingPage.tsx +549 -0
  220. package/src/landing/PrivacyPage.tsx +117 -0
  221. package/src/landing/SecurityPage.tsx +42 -0
  222. package/src/landing/StaticPage.tsx +165 -0
  223. package/src/landing/TermsPage.tsx +125 -0
  224. package/src/landing/blogData.ts +312 -0
  225. package/src/landing/index.ts +18 -0
  226. package/src/landing/styles.css +3673 -0
  227. package/src/lib/agent-merge.test.ts +43 -0
  228. package/src/lib/agent-merge.ts +35 -0
  229. package/src/lib/api.ts +1294 -0
  230. package/src/lib/cloudApi.ts +893 -0
  231. package/src/lib/colors.test.ts +175 -0
  232. package/src/lib/colors.ts +218 -0
  233. package/src/lib/config.ts +109 -0
  234. package/src/lib/hierarchy.ts +242 -0
  235. package/src/lib/stuckDetection.ts +142 -0
  236. package/src/lib/useUrlRouting.ts +190 -0
  237. package/src/types/index.ts +317 -0
  238. package/src/types/threading.ts +7 -0
  239. package/out/_next/static/chunks/285-dc644487a8d6500d.js +0 -1
  240. package/out/_next/static/css/4c58d9cf493aa626.css +0 -1
  241. /package/out/_next/static/{AqelRhy1vr2nBUcU0Iqcp → IxfA6RZu4trcsEMYlkQra}/_buildManifest.js +0 -0
  242. /package/out/_next/static/{AqelRhy1vr2nBUcU0Iqcp → IxfA6RZu4trcsEMYlkQra}/_ssgManifest.js +0 -0
  243. /package/out/_next/static/chunks/{528-d375bc8b46912d2c.js → 528-f5f676996d613c25.js} +0 -0
  244. /package/out/_next/static/chunks/app/blog/let-them-cook-multi-agent-orchestration/{page-a58308f43557b908.js → page-b194f207fbd91862.js} +0 -0
@@ -0,0 +1,922 @@
1
+ /**
2
+ * Billing Panel Component
3
+ *
4
+ * Shows current subscription status, usage, and billing management options.
5
+ */
6
+
7
+ import React, { useState, useEffect, useCallback } from 'react';
8
+ import { PricingPlans, type Plan } from './PricingPlans';
9
+
10
+ export interface Subscription {
11
+ id: string;
12
+ tier: string;
13
+ status: string;
14
+ currentPeriodStart: Date;
15
+ currentPeriodEnd: Date;
16
+ cancelAtPeriodEnd: boolean;
17
+ billingInterval: 'month' | 'year';
18
+ }
19
+
20
+ export interface Invoice {
21
+ id: string;
22
+ amountDue: number;
23
+ amountPaid: number;
24
+ status: string;
25
+ invoicePdf?: string;
26
+ hostedInvoiceUrl?: string;
27
+ periodStart: Date;
28
+ periodEnd: Date;
29
+ createdAt: Date;
30
+ }
31
+
32
+ export interface PaymentMethod {
33
+ id: string;
34
+ type: string;
35
+ isDefault: boolean;
36
+ card?: {
37
+ brand: string;
38
+ last4: string;
39
+ expMonth: number;
40
+ expYear: number;
41
+ };
42
+ }
43
+
44
+ export interface BillingPanelProps {
45
+ apiUrl?: string;
46
+ onClose?: () => void;
47
+ }
48
+
49
+ export function BillingPanel({ apiUrl = '/api/billing', onClose }: BillingPanelProps) {
50
+ const [activeTab, setActiveTab] = useState<'overview' | 'plans' | 'invoices'>('overview');
51
+ const [subscription, setSubscription] = useState<Subscription | null>(null);
52
+ const [plans, setPlans] = useState<Plan[]>([]);
53
+ const [invoices, setInvoices] = useState<Invoice[]>([]);
54
+ const [paymentMethods, setPaymentMethods] = useState<PaymentMethod[]>([]);
55
+ const [isLoading, setIsLoading] = useState(true);
56
+ const [isProcessing, setIsProcessing] = useState(false);
57
+ const [error, setError] = useState<string | null>(null);
58
+
59
+ // Fetch billing data
60
+ const fetchBillingData = useCallback(async () => {
61
+ setIsLoading(true);
62
+ setError(null);
63
+
64
+ try {
65
+ // Fetch plans
66
+ const plansResponse = await fetch(`${apiUrl}/plans`);
67
+ const plansData = await plansResponse.json();
68
+ setPlans(plansData.plans);
69
+
70
+ // Fetch subscription
71
+ const subResponse = await fetch(`${apiUrl}/subscription`);
72
+ const subData = await subResponse.json();
73
+ if (subData.subscription) {
74
+ setSubscription({
75
+ ...subData.subscription,
76
+ currentPeriodStart: new Date(subData.subscription.currentPeriodStart),
77
+ currentPeriodEnd: new Date(subData.subscription.currentPeriodEnd),
78
+ });
79
+ }
80
+ if (subData.customer?.paymentMethods) {
81
+ setPaymentMethods(subData.customer.paymentMethods);
82
+ }
83
+ if (subData.customer?.invoices) {
84
+ setInvoices(
85
+ subData.customer.invoices.map((inv: Invoice) => ({
86
+ ...inv,
87
+ periodStart: new Date(inv.periodStart),
88
+ periodEnd: new Date(inv.periodEnd),
89
+ createdAt: new Date(inv.createdAt),
90
+ }))
91
+ );
92
+ }
93
+ } catch (err) {
94
+ setError(err instanceof Error ? err.message : 'Failed to load billing data');
95
+ } finally {
96
+ setIsLoading(false);
97
+ }
98
+ }, [apiUrl]);
99
+
100
+ useEffect(() => {
101
+ fetchBillingData();
102
+ }, [fetchBillingData]);
103
+
104
+ // Handle plan selection
105
+ const handleSelectPlan = async (planId: string, interval: 'month' | 'year') => {
106
+ if (planId === 'enterprise') {
107
+ // Redirect to contact sales
108
+ window.open('mailto:sales@agent-relay.com?subject=Enterprise%20Plan%20Inquiry', '_blank');
109
+ return;
110
+ }
111
+
112
+ setIsProcessing(true);
113
+ setError(null);
114
+
115
+ try {
116
+ // Check if upgrading or changing plan
117
+ if (subscription && planId !== 'free') {
118
+ // Change existing subscription
119
+ const response = await fetch(`${apiUrl}/change`, {
120
+ method: 'POST',
121
+ headers: { 'Content-Type': 'application/json' },
122
+ body: JSON.stringify({ tier: planId, interval }),
123
+ });
124
+
125
+ if (!response.ok) {
126
+ const data = await response.json();
127
+ throw new Error(data.error || 'Failed to change subscription');
128
+ }
129
+
130
+ await fetchBillingData();
131
+ } else if (planId === 'free' && subscription) {
132
+ // Cancel subscription
133
+ const response = await fetch(`${apiUrl}/cancel`, {
134
+ method: 'POST',
135
+ });
136
+
137
+ if (!response.ok) {
138
+ const data = await response.json();
139
+ throw new Error(data.error || 'Failed to cancel subscription');
140
+ }
141
+
142
+ await fetchBillingData();
143
+ } else {
144
+ // Create new subscription via checkout
145
+ const response = await fetch(`${apiUrl}/checkout`, {
146
+ method: 'POST',
147
+ headers: { 'Content-Type': 'application/json' },
148
+ body: JSON.stringify({ tier: planId, interval }),
149
+ });
150
+
151
+ if (!response.ok) {
152
+ const data = await response.json();
153
+ throw new Error(data.error || 'Failed to create checkout session');
154
+ }
155
+
156
+ const { url } = await response.json();
157
+ window.location.href = url;
158
+ }
159
+ } catch (err) {
160
+ setError(err instanceof Error ? err.message : 'An error occurred');
161
+ } finally {
162
+ setIsProcessing(false);
163
+ }
164
+ };
165
+
166
+ // Handle resume subscription
167
+ const handleResumeSubscription = async () => {
168
+ setIsProcessing(true);
169
+ setError(null);
170
+
171
+ try {
172
+ const response = await fetch(`${apiUrl}/resume`, {
173
+ method: 'POST',
174
+ });
175
+
176
+ if (!response.ok) {
177
+ const data = await response.json();
178
+ throw new Error(data.error || 'Failed to resume subscription');
179
+ }
180
+
181
+ await fetchBillingData();
182
+ } catch (err) {
183
+ setError(err instanceof Error ? err.message : 'An error occurred');
184
+ } finally {
185
+ setIsProcessing(false);
186
+ }
187
+ };
188
+
189
+ // Handle open billing portal
190
+ const handleOpenPortal = async () => {
191
+ setIsProcessing(true);
192
+ setError(null);
193
+
194
+ try {
195
+ const response = await fetch(`${apiUrl}/portal`, {
196
+ method: 'POST',
197
+ });
198
+
199
+ if (!response.ok) {
200
+ const data = await response.json();
201
+ throw new Error(data.error || 'Failed to open billing portal');
202
+ }
203
+
204
+ const { url } = await response.json();
205
+ window.open(url, '_blank');
206
+ } catch (err) {
207
+ setError(err instanceof Error ? err.message : 'An error occurred');
208
+ } finally {
209
+ setIsProcessing(false);
210
+ }
211
+ };
212
+
213
+ const currentTier = subscription?.tier || 'free';
214
+ const currentPlan = plans.find((p) => p.id === currentTier);
215
+
216
+ const formatDate = (date: Date) => {
217
+ return date.toLocaleDateString('en-US', {
218
+ year: 'numeric',
219
+ month: 'long',
220
+ day: 'numeric',
221
+ });
222
+ };
223
+
224
+ const formatAmount = (cents: number) => {
225
+ return `$${(cents / 100).toFixed(2)}`;
226
+ };
227
+
228
+ if (isLoading) {
229
+ return (
230
+ <div className="billing-panel">
231
+ <div className="billing-loading">
232
+ <LoadingSpinner />
233
+ <p>Loading billing information...</p>
234
+ </div>
235
+ </div>
236
+ );
237
+ }
238
+
239
+ return (
240
+ <div className="billing-panel">
241
+ {/* Header */}
242
+ <div className="billing-header">
243
+ <h2>Billing & Subscription</h2>
244
+ {onClose && (
245
+ <button className="billing-close" onClick={onClose}>
246
+ <CloseIcon />
247
+ </button>
248
+ )}
249
+ </div>
250
+
251
+ {/* Error */}
252
+ {error && (
253
+ <div className="billing-error">
254
+ <span>{error}</span>
255
+ <button onClick={() => setError(null)}>&times;</button>
256
+ </div>
257
+ )}
258
+
259
+ {/* Tabs */}
260
+ <div className="billing-tabs">
261
+ <button
262
+ className={`billing-tab ${activeTab === 'overview' ? 'active' : ''}`}
263
+ onClick={() => setActiveTab('overview')}
264
+ >
265
+ Overview
266
+ </button>
267
+ <button
268
+ className={`billing-tab ${activeTab === 'plans' ? 'active' : ''}`}
269
+ onClick={() => setActiveTab('plans')}
270
+ >
271
+ Plans
272
+ </button>
273
+ <button
274
+ className={`billing-tab ${activeTab === 'invoices' ? 'active' : ''}`}
275
+ onClick={() => setActiveTab('invoices')}
276
+ >
277
+ Invoices
278
+ </button>
279
+ </div>
280
+
281
+ {/* Tab Content */}
282
+ <div className="billing-content">
283
+ {activeTab === 'overview' && (
284
+ <div className="billing-overview">
285
+ {/* Current Plan */}
286
+ <div className="overview-section">
287
+ <h3>Current Plan</h3>
288
+ <div className="current-plan-card">
289
+ <div className="plan-info">
290
+ <span className="plan-name">{currentPlan?.name || 'Free'}</span>
291
+ <span className="plan-tier-badge">{currentTier}</span>
292
+ </div>
293
+ {subscription && (
294
+ <div className="plan-details">
295
+ <p>
296
+ <strong>Status:</strong>{' '}
297
+ <span className={`status-${subscription.status}`}>
298
+ {subscription.status}
299
+ </span>
300
+ </p>
301
+ <p>
302
+ <strong>Billing:</strong> {subscription.billingInterval === 'year' ? 'Annual' : 'Monthly'}
303
+ </p>
304
+ <p>
305
+ <strong>Current Period:</strong>{' '}
306
+ {formatDate(subscription.currentPeriodStart)} - {formatDate(subscription.currentPeriodEnd)}
307
+ </p>
308
+ {subscription.cancelAtPeriodEnd && (
309
+ <div className="cancel-notice">
310
+ <p>Your subscription will be canceled on {formatDate(subscription.currentPeriodEnd)}</p>
311
+ <button
312
+ className="btn-resume"
313
+ onClick={handleResumeSubscription}
314
+ disabled={isProcessing}
315
+ >
316
+ {isProcessing ? 'Processing...' : 'Resume Subscription'}
317
+ </button>
318
+ </div>
319
+ )}
320
+ </div>
321
+ )}
322
+ {!subscription && (
323
+ <p className="free-notice">
324
+ You're on the free plan. Upgrade to unlock more features!
325
+ </p>
326
+ )}
327
+ </div>
328
+ </div>
329
+
330
+ {/* Usage */}
331
+ {currentPlan && (
332
+ <div className="overview-section">
333
+ <h3>Usage & Limits</h3>
334
+ <div className="usage-grid">
335
+ <UsageItem
336
+ label="Workspaces"
337
+ current={0}
338
+ limit={currentPlan.limits.maxWorkspaces}
339
+ />
340
+ <UsageItem
341
+ label="Agents per Workspace"
342
+ current={0}
343
+ limit={currentPlan.limits.maxAgentsPerWorkspace}
344
+ />
345
+ <UsageItem
346
+ label="Team Members"
347
+ current={1}
348
+ limit={currentPlan.limits.maxTeamMembers}
349
+ />
350
+ <UsageItem
351
+ label="Storage"
352
+ current={0}
353
+ limit={currentPlan.limits.maxStorageGB}
354
+ unit="GB"
355
+ />
356
+ <UsageItem
357
+ label="Compute Hours"
358
+ current={0}
359
+ limit={currentPlan.limits.maxComputeHoursPerMonth}
360
+ unit="hrs/mo"
361
+ />
362
+ </div>
363
+ </div>
364
+ )}
365
+
366
+ {/* Payment Method */}
367
+ {paymentMethods.length > 0 && (
368
+ <div className="overview-section">
369
+ <h3>Payment Method</h3>
370
+ <div className="payment-methods">
371
+ {paymentMethods.map((pm) => (
372
+ <div key={pm.id} className={`payment-method ${pm.isDefault ? 'default' : ''}`}>
373
+ {pm.card && (
374
+ <>
375
+ <CardIcon brand={pm.card.brand} />
376
+ <span className="card-info">
377
+ {pm.card.brand.charAt(0).toUpperCase() + pm.card.brand.slice(1)} •••• {pm.card.last4}
378
+ </span>
379
+ <span className="card-expiry">
380
+ Expires {pm.card.expMonth}/{pm.card.expYear}
381
+ </span>
382
+ {pm.isDefault && <span className="default-badge">Default</span>}
383
+ </>
384
+ )}
385
+ </div>
386
+ ))}
387
+ </div>
388
+ <button className="btn-manage-billing" onClick={handleOpenPortal} disabled={isProcessing}>
389
+ {isProcessing ? 'Opening...' : 'Manage Payment Methods'}
390
+ </button>
391
+ </div>
392
+ )}
393
+ </div>
394
+ )}
395
+
396
+ {activeTab === 'plans' && (
397
+ <PricingPlans
398
+ plans={plans}
399
+ currentPlan={currentTier}
400
+ onSelectPlan={handleSelectPlan}
401
+ isLoading={isProcessing}
402
+ />
403
+ )}
404
+
405
+ {activeTab === 'invoices' && (
406
+ <div className="billing-invoices">
407
+ {invoices.length === 0 ? (
408
+ <div className="no-invoices">
409
+ <p>No invoices yet</p>
410
+ </div>
411
+ ) : (
412
+ <table className="invoices-table">
413
+ <thead>
414
+ <tr>
415
+ <th>Date</th>
416
+ <th>Period</th>
417
+ <th>Amount</th>
418
+ <th>Status</th>
419
+ <th>Actions</th>
420
+ </tr>
421
+ </thead>
422
+ <tbody>
423
+ {invoices.map((invoice) => (
424
+ <tr key={invoice.id}>
425
+ <td>{formatDate(invoice.createdAt)}</td>
426
+ <td>
427
+ {formatDate(invoice.periodStart)} - {formatDate(invoice.periodEnd)}
428
+ </td>
429
+ <td>{formatAmount(invoice.amountPaid || invoice.amountDue)}</td>
430
+ <td>
431
+ <span className={`invoice-status status-${invoice.status}`}>
432
+ {invoice.status}
433
+ </span>
434
+ </td>
435
+ <td>
436
+ {invoice.invoicePdf && (
437
+ <a href={invoice.invoicePdf} target="_blank" rel="noopener noreferrer">
438
+ PDF
439
+ </a>
440
+ )}
441
+ {invoice.hostedInvoiceUrl && (
442
+ <a href={invoice.hostedInvoiceUrl} target="_blank" rel="noopener noreferrer">
443
+ View
444
+ </a>
445
+ )}
446
+ </td>
447
+ </tr>
448
+ ))}
449
+ </tbody>
450
+ </table>
451
+ )}
452
+ </div>
453
+ )}
454
+ </div>
455
+ </div>
456
+ );
457
+ }
458
+
459
+ // Helper components
460
+ function UsageItem({
461
+ label,
462
+ current,
463
+ limit,
464
+ unit,
465
+ }: {
466
+ label: string;
467
+ current: number;
468
+ limit: number;
469
+ unit?: string;
470
+ }) {
471
+ const isUnlimited = limit === -1;
472
+ const percentage = isUnlimited ? 0 : (current / limit) * 100;
473
+
474
+ return (
475
+ <div className="usage-item">
476
+ <div className="usage-header">
477
+ <span className="usage-label">{label}</span>
478
+ <span className="usage-value">
479
+ {current}{unit ? ` ${unit}` : ''} / {isUnlimited ? 'Unlimited' : `${limit}${unit ? ` ${unit}` : ''}`}
480
+ </span>
481
+ </div>
482
+ <div className="usage-bar">
483
+ <div
484
+ className={`usage-fill ${percentage > 80 ? 'warning' : ''} ${percentage > 95 ? 'critical' : ''}`}
485
+ style={{ width: `${Math.min(percentage, 100)}%` }}
486
+ />
487
+ </div>
488
+ </div>
489
+ );
490
+ }
491
+
492
+ function CardIcon({ brand }: { brand: string }) {
493
+ return (
494
+ <svg width="32" height="24" viewBox="0 0 32 24" fill="none" className="card-icon">
495
+ <rect width="32" height="24" rx="4" fill="#2a2a3e" />
496
+ <text x="16" y="14" textAnchor="middle" fill="#8d8d8e" fontSize="8">
497
+ {brand.toUpperCase()}
498
+ </text>
499
+ </svg>
500
+ );
501
+ }
502
+
503
+ function LoadingSpinner() {
504
+ return (
505
+ <svg className="spinner" width="24" height="24" viewBox="0 0 24 24">
506
+ <circle
507
+ cx="12"
508
+ cy="12"
509
+ r="10"
510
+ stroke="currentColor"
511
+ strokeWidth="2"
512
+ fill="none"
513
+ strokeDasharray="32"
514
+ strokeLinecap="round"
515
+ />
516
+ </svg>
517
+ );
518
+ }
519
+
520
+ function CloseIcon() {
521
+ return (
522
+ <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
523
+ <line x1="18" y1="6" x2="6" y2="18" />
524
+ <line x1="6" y1="6" x2="18" y2="18" />
525
+ </svg>
526
+ );
527
+ }
528
+
529
+ export const billingPanelStyles = `
530
+ .billing-panel {
531
+ background: #1a1a2e;
532
+ border: 1px solid #2a2a3e;
533
+ border-radius: 12px;
534
+ max-width: 1000px;
535
+ margin: 0 auto;
536
+ overflow: hidden;
537
+ }
538
+
539
+ .billing-header {
540
+ display: flex;
541
+ align-items: center;
542
+ justify-content: space-between;
543
+ padding: 20px 24px;
544
+ border-bottom: 1px solid #2a2a3e;
545
+ }
546
+
547
+ .billing-header h2 {
548
+ margin: 0;
549
+ font-size: 18px;
550
+ font-weight: 600;
551
+ color: #e8e8e8;
552
+ }
553
+
554
+ .billing-close {
555
+ background: transparent;
556
+ border: none;
557
+ color: #666;
558
+ cursor: pointer;
559
+ padding: 4px;
560
+ display: flex;
561
+ border-radius: 4px;
562
+ transition: all 0.2s;
563
+ }
564
+
565
+ .billing-close:hover {
566
+ background: rgba(255, 255, 255, 0.1);
567
+ color: #e8e8e8;
568
+ }
569
+
570
+ .billing-loading {
571
+ display: flex;
572
+ flex-direction: column;
573
+ align-items: center;
574
+ justify-content: center;
575
+ padding: 60px 24px;
576
+ color: #8d8d8e;
577
+ }
578
+
579
+ .billing-loading .spinner {
580
+ animation: spin 1s linear infinite;
581
+ margin-bottom: 16px;
582
+ color: #00c896;
583
+ }
584
+
585
+ @keyframes spin {
586
+ from { transform: rotate(0deg); }
587
+ to { transform: rotate(360deg); }
588
+ }
589
+
590
+ .billing-error {
591
+ display: flex;
592
+ align-items: center;
593
+ justify-content: space-between;
594
+ padding: 12px 24px;
595
+ background: rgba(239, 68, 68, 0.1);
596
+ border-bottom: 1px solid rgba(239, 68, 68, 0.3);
597
+ color: #ef4444;
598
+ font-size: 14px;
599
+ }
600
+
601
+ .billing-error button {
602
+ background: none;
603
+ border: none;
604
+ color: #ef4444;
605
+ cursor: pointer;
606
+ font-size: 18px;
607
+ }
608
+
609
+ .billing-tabs {
610
+ display: flex;
611
+ border-bottom: 1px solid #2a2a3e;
612
+ }
613
+
614
+ .billing-tab {
615
+ flex: 1;
616
+ padding: 14px 20px;
617
+ background: transparent;
618
+ border: none;
619
+ border-bottom: 2px solid transparent;
620
+ color: #8d8d8e;
621
+ font-size: 14px;
622
+ cursor: pointer;
623
+ transition: all 0.2s;
624
+ }
625
+
626
+ .billing-tab:hover {
627
+ color: #e8e8e8;
628
+ background: rgba(255, 255, 255, 0.02);
629
+ }
630
+
631
+ .billing-tab.active {
632
+ color: #00c896;
633
+ border-bottom-color: #00c896;
634
+ }
635
+
636
+ .billing-content {
637
+ padding: 24px;
638
+ }
639
+
640
+ .billing-overview {
641
+ display: flex;
642
+ flex-direction: column;
643
+ gap: 32px;
644
+ }
645
+
646
+ .overview-section h3 {
647
+ margin: 0 0 16px;
648
+ font-size: 16px;
649
+ font-weight: 600;
650
+ color: #e8e8e8;
651
+ }
652
+
653
+ .current-plan-card {
654
+ background: #222234;
655
+ border: 1px solid #2a2a3e;
656
+ border-radius: 8px;
657
+ padding: 20px;
658
+ }
659
+
660
+ .plan-info {
661
+ display: flex;
662
+ align-items: center;
663
+ gap: 12px;
664
+ margin-bottom: 16px;
665
+ }
666
+
667
+ .plan-info .plan-name {
668
+ font-size: 20px;
669
+ font-weight: 600;
670
+ color: #e8e8e8;
671
+ }
672
+
673
+ .plan-tier-badge {
674
+ padding: 4px 10px;
675
+ background: #00c896;
676
+ color: #1a1a2e;
677
+ border-radius: 4px;
678
+ font-size: 12px;
679
+ font-weight: 500;
680
+ text-transform: capitalize;
681
+ }
682
+
683
+ .plan-details p {
684
+ margin: 8px 0;
685
+ font-size: 14px;
686
+ color: #b8b8b8;
687
+ }
688
+
689
+ .plan-details strong {
690
+ color: #e8e8e8;
691
+ }
692
+
693
+ .status-active { color: #00c896; }
694
+ .status-past_due { color: #f59e0b; }
695
+ .status-canceled { color: #ef4444; }
696
+ .status-trialing { color: #3b82f6; }
697
+
698
+ .cancel-notice {
699
+ margin-top: 16px;
700
+ padding: 12px;
701
+ background: rgba(239, 68, 68, 0.1);
702
+ border: 1px solid rgba(239, 68, 68, 0.3);
703
+ border-radius: 6px;
704
+ }
705
+
706
+ .cancel-notice p {
707
+ color: #ef4444;
708
+ margin-bottom: 12px;
709
+ }
710
+
711
+ .btn-resume {
712
+ padding: 8px 16px;
713
+ background: #00c896;
714
+ color: #1a1a2e;
715
+ border: none;
716
+ border-radius: 6px;
717
+ font-size: 14px;
718
+ cursor: pointer;
719
+ transition: all 0.2s;
720
+ }
721
+
722
+ .btn-resume:hover:not(:disabled) {
723
+ background: #00a87d;
724
+ }
725
+
726
+ .btn-resume:disabled {
727
+ opacity: 0.6;
728
+ cursor: not-allowed;
729
+ }
730
+
731
+ .free-notice {
732
+ color: #8d8d8e;
733
+ font-size: 14px;
734
+ }
735
+
736
+ .usage-grid {
737
+ display: grid;
738
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
739
+ gap: 16px;
740
+ }
741
+
742
+ .usage-item {
743
+ background: #222234;
744
+ border: 1px solid #2a2a3e;
745
+ border-radius: 8px;
746
+ padding: 16px;
747
+ }
748
+
749
+ .usage-header {
750
+ display: flex;
751
+ justify-content: space-between;
752
+ margin-bottom: 8px;
753
+ }
754
+
755
+ .usage-label {
756
+ font-size: 13px;
757
+ color: #8d8d8e;
758
+ }
759
+
760
+ .usage-value {
761
+ font-size: 13px;
762
+ color: #e8e8e8;
763
+ font-weight: 500;
764
+ }
765
+
766
+ .usage-bar {
767
+ height: 6px;
768
+ background: #2a2a3e;
769
+ border-radius: 3px;
770
+ overflow: hidden;
771
+ }
772
+
773
+ .usage-fill {
774
+ height: 100%;
775
+ background: #00c896;
776
+ border-radius: 3px;
777
+ transition: width 0.3s;
778
+ }
779
+
780
+ .usage-fill.warning {
781
+ background: #f59e0b;
782
+ }
783
+
784
+ .usage-fill.critical {
785
+ background: #ef4444;
786
+ }
787
+
788
+ .payment-methods {
789
+ display: flex;
790
+ flex-direction: column;
791
+ gap: 12px;
792
+ margin-bottom: 16px;
793
+ }
794
+
795
+ .payment-method {
796
+ display: flex;
797
+ align-items: center;
798
+ gap: 12px;
799
+ padding: 12px 16px;
800
+ background: #222234;
801
+ border: 1px solid #2a2a3e;
802
+ border-radius: 8px;
803
+ }
804
+
805
+ .payment-method.default {
806
+ border-color: #00c896;
807
+ }
808
+
809
+ .card-icon {
810
+ flex-shrink: 0;
811
+ }
812
+
813
+ .card-info {
814
+ font-size: 14px;
815
+ color: #e8e8e8;
816
+ }
817
+
818
+ .card-expiry {
819
+ font-size: 12px;
820
+ color: #8d8d8e;
821
+ margin-left: auto;
822
+ }
823
+
824
+ .default-badge {
825
+ padding: 2px 8px;
826
+ background: #00c896;
827
+ color: #1a1a2e;
828
+ border-radius: 4px;
829
+ font-size: 11px;
830
+ font-weight: 500;
831
+ }
832
+
833
+ .btn-manage-billing {
834
+ padding: 10px 16px;
835
+ background: transparent;
836
+ border: 1px solid #3a3a4e;
837
+ border-radius: 6px;
838
+ color: #e8e8e8;
839
+ font-size: 14px;
840
+ cursor: pointer;
841
+ transition: all 0.2s;
842
+ }
843
+
844
+ .btn-manage-billing:hover:not(:disabled) {
845
+ background: rgba(255, 255, 255, 0.05);
846
+ border-color: #4a4a5e;
847
+ }
848
+
849
+ .btn-manage-billing:disabled {
850
+ opacity: 0.6;
851
+ cursor: not-allowed;
852
+ }
853
+
854
+ .billing-invoices {
855
+ min-height: 200px;
856
+ }
857
+
858
+ .no-invoices {
859
+ display: flex;
860
+ align-items: center;
861
+ justify-content: center;
862
+ padding: 60px;
863
+ color: #8d8d8e;
864
+ }
865
+
866
+ .invoices-table {
867
+ width: 100%;
868
+ border-collapse: collapse;
869
+ }
870
+
871
+ .invoices-table th,
872
+ .invoices-table td {
873
+ padding: 12px 16px;
874
+ text-align: left;
875
+ border-bottom: 1px solid #2a2a3e;
876
+ }
877
+
878
+ .invoices-table th {
879
+ font-size: 12px;
880
+ font-weight: 500;
881
+ color: #8d8d8e;
882
+ text-transform: uppercase;
883
+ letter-spacing: 0.5px;
884
+ }
885
+
886
+ .invoices-table td {
887
+ font-size: 14px;
888
+ color: #e8e8e8;
889
+ }
890
+
891
+ .invoice-status {
892
+ padding: 4px 8px;
893
+ border-radius: 4px;
894
+ font-size: 12px;
895
+ text-transform: capitalize;
896
+ }
897
+
898
+ .invoice-status.status-paid {
899
+ background: rgba(0, 200, 150, 0.1);
900
+ color: #00c896;
901
+ }
902
+
903
+ .invoice-status.status-open {
904
+ background: rgba(59, 130, 246, 0.1);
905
+ color: #3b82f6;
906
+ }
907
+
908
+ .invoice-status.status-void {
909
+ background: rgba(107, 114, 128, 0.1);
910
+ color: #6b7280;
911
+ }
912
+
913
+ .invoices-table a {
914
+ color: #00c896;
915
+ text-decoration: none;
916
+ margin-right: 12px;
917
+ }
918
+
919
+ .invoices-table a:hover {
920
+ text-decoration: underline;
921
+ }
922
+ `;