@nice2dev/licensing 1.0.10

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.
package/dist/index.cjs ADDED
@@ -0,0 +1,4352 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
+ const FeatureGate = require("./FeatureGate-Cs1zphXs.cjs");
4
+ const jsxRuntime = require("react/jsx-runtime");
5
+ const React = require("react");
6
+ const HEARTBEAT_INTERVAL_MS = 5 * 60 * 1e3;
7
+ class SeatManager {
8
+ constructor(serverUrl, apiKey) {
9
+ this.serverUrl = serverUrl;
10
+ this.apiKey = apiKey;
11
+ }
12
+ /**
13
+ * Acquire a seat for the current user
14
+ */
15
+ async acquireSeat(licenseKey, userId, displayName, email) {
16
+ const machineId = await FeatureGate.generateMachineId();
17
+ try {
18
+ const response = await fetch(`${this.serverUrl}/seats/acquire`, {
19
+ method: "POST",
20
+ headers: {
21
+ "Content-Type": "application/json",
22
+ ...this.apiKey ? { "X-API-Key": this.apiKey } : {}
23
+ },
24
+ body: JSON.stringify({
25
+ licenseKey,
26
+ userId,
27
+ displayName,
28
+ email,
29
+ machineId
30
+ })
31
+ });
32
+ const data = await response.json();
33
+ if (!response.ok) {
34
+ return {
35
+ success: false,
36
+ error: data.error || "Failed to acquire seat"
37
+ };
38
+ }
39
+ this.currentSeat = data.seat;
40
+ this.startHeartbeat(licenseKey);
41
+ return {
42
+ success: true,
43
+ seat: data.seat
44
+ };
45
+ } catch (error) {
46
+ return {
47
+ success: false,
48
+ error: "Network error"
49
+ };
50
+ }
51
+ }
52
+ /**
53
+ * Release current seat
54
+ */
55
+ async releaseSeat(licenseKey) {
56
+ if (!this.currentSeat) {
57
+ return true;
58
+ }
59
+ this.stopHeartbeat();
60
+ try {
61
+ const response = await fetch(`${this.serverUrl}/seats/release`, {
62
+ method: "POST",
63
+ headers: {
64
+ "Content-Type": "application/json",
65
+ ...this.apiKey ? { "X-API-Key": this.apiKey } : {}
66
+ },
67
+ body: JSON.stringify({
68
+ licenseKey,
69
+ seatId: this.currentSeat.seatId
70
+ })
71
+ });
72
+ if (response.ok) {
73
+ this.currentSeat = void 0;
74
+ return true;
75
+ }
76
+ return false;
77
+ } catch {
78
+ return false;
79
+ }
80
+ }
81
+ /**
82
+ * Get all active seats for a license
83
+ */
84
+ async getActiveSeats(licenseKey) {
85
+ try {
86
+ const response = await fetch(
87
+ `${this.serverUrl}/seats/list?licenseKey=${encodeURIComponent(licenseKey)}`,
88
+ {
89
+ headers: {
90
+ ...this.apiKey ? { "X-API-Key": this.apiKey } : {}
91
+ }
92
+ }
93
+ );
94
+ if (!response.ok) {
95
+ return [];
96
+ }
97
+ const data = await response.json();
98
+ return data.seats || [];
99
+ } catch {
100
+ return [];
101
+ }
102
+ }
103
+ /**
104
+ * Force release a seat (admin function)
105
+ */
106
+ async forceReleaseSeat(licenseKey, seatId) {
107
+ try {
108
+ const response = await fetch(`${this.serverUrl}/seats/force-release`, {
109
+ method: "POST",
110
+ headers: {
111
+ "Content-Type": "application/json",
112
+ ...this.apiKey ? { "X-API-Key": this.apiKey } : {}
113
+ },
114
+ body: JSON.stringify({
115
+ licenseKey,
116
+ seatId
117
+ })
118
+ });
119
+ return response.ok;
120
+ } catch {
121
+ return false;
122
+ }
123
+ }
124
+ /**
125
+ * Check if seat limit is reached
126
+ */
127
+ async canAcquireSeat(license) {
128
+ if (license.maxSeats === null) {
129
+ return true;
130
+ }
131
+ const activeSeats = await this.getActiveSeats(license.key);
132
+ return activeSeats.length < license.maxSeats;
133
+ }
134
+ /**
135
+ * Start heartbeat to keep seat active
136
+ */
137
+ startHeartbeat(licenseKey) {
138
+ this.stopHeartbeat();
139
+ this.heartbeatInterval = setInterval(async () => {
140
+ if (!this.currentSeat) {
141
+ this.stopHeartbeat();
142
+ return;
143
+ }
144
+ try {
145
+ await fetch(`${this.serverUrl}/seats/heartbeat`, {
146
+ method: "POST",
147
+ headers: {
148
+ "Content-Type": "application/json",
149
+ ...this.apiKey ? { "X-API-Key": this.apiKey } : {}
150
+ },
151
+ body: JSON.stringify({
152
+ licenseKey,
153
+ seatId: this.currentSeat.seatId
154
+ })
155
+ });
156
+ } catch {
157
+ }
158
+ }, HEARTBEAT_INTERVAL_MS);
159
+ }
160
+ /**
161
+ * Stop heartbeat
162
+ */
163
+ stopHeartbeat() {
164
+ if (this.heartbeatInterval) {
165
+ clearInterval(this.heartbeatInterval);
166
+ this.heartbeatInterval = void 0;
167
+ }
168
+ }
169
+ /**
170
+ * Get current seat info
171
+ */
172
+ getCurrentSeat() {
173
+ return this.currentSeat;
174
+ }
175
+ /**
176
+ * Cleanup on unmount/close
177
+ */
178
+ async cleanup(licenseKey) {
179
+ await this.releaseSeat(licenseKey);
180
+ }
181
+ }
182
+ class FloatingLicenseManager {
183
+ constructor(serverUrl, apiKey) {
184
+ this.serverUrl = serverUrl;
185
+ this.apiKey = apiKey;
186
+ }
187
+ /**
188
+ * Acquire a floating license
189
+ */
190
+ async acquireLease(licenseKey) {
191
+ const machineId = await FeatureGate.generateMachineId();
192
+ try {
193
+ const response = await fetch(`${this.serverUrl}/floating/acquire`, {
194
+ method: "POST",
195
+ headers: {
196
+ "Content-Type": "application/json",
197
+ ...this.apiKey ? { "X-API-Key": this.apiKey } : {}
198
+ },
199
+ body: JSON.stringify({
200
+ licenseKey,
201
+ machineId
202
+ })
203
+ });
204
+ const data = await response.json();
205
+ if (!response.ok) {
206
+ return {
207
+ success: false,
208
+ error: data.error || "No floating licenses available"
209
+ };
210
+ }
211
+ this.leaseId = data.leaseId;
212
+ this.startLeaseRenewal(licenseKey);
213
+ return {
214
+ success: true,
215
+ leaseId: data.leaseId,
216
+ expiresAt: data.expiresAt
217
+ };
218
+ } catch (error) {
219
+ return {
220
+ success: false,
221
+ error: "Network error"
222
+ };
223
+ }
224
+ }
225
+ /**
226
+ * Release floating license
227
+ */
228
+ async releaseLease(licenseKey) {
229
+ if (!this.leaseId) {
230
+ return true;
231
+ }
232
+ this.stopLeaseRenewal();
233
+ try {
234
+ const response = await fetch(`${this.serverUrl}/floating/release`, {
235
+ method: "POST",
236
+ headers: {
237
+ "Content-Type": "application/json",
238
+ ...this.apiKey ? { "X-API-Key": this.apiKey } : {}
239
+ },
240
+ body: JSON.stringify({
241
+ licenseKey,
242
+ leaseId: this.leaseId
243
+ })
244
+ });
245
+ if (response.ok) {
246
+ this.leaseId = void 0;
247
+ return true;
248
+ }
249
+ return false;
250
+ } catch {
251
+ return false;
252
+ }
253
+ }
254
+ /**
255
+ * Get floating license status
256
+ */
257
+ async getFloatingStatus(licenseKey) {
258
+ try {
259
+ const response = await fetch(
260
+ `${this.serverUrl}/floating/status?licenseKey=${encodeURIComponent(licenseKey)}`,
261
+ {
262
+ headers: {
263
+ ...this.apiKey ? { "X-API-Key": this.apiKey } : {}
264
+ }
265
+ }
266
+ );
267
+ if (!response.ok) {
268
+ return { total: 0, available: 0, leases: [] };
269
+ }
270
+ return response.json();
271
+ } catch {
272
+ return { total: 0, available: 0, leases: [] };
273
+ }
274
+ }
275
+ /**
276
+ * Start lease renewal interval
277
+ */
278
+ startLeaseRenewal(licenseKey) {
279
+ this.stopLeaseRenewal();
280
+ this.renewInterval = setInterval(
281
+ async () => {
282
+ if (!this.leaseId) {
283
+ this.stopLeaseRenewal();
284
+ return;
285
+ }
286
+ try {
287
+ const response = await fetch(`${this.serverUrl}/floating/renew`, {
288
+ method: "POST",
289
+ headers: {
290
+ "Content-Type": "application/json",
291
+ ...this.apiKey ? { "X-API-Key": this.apiKey } : {}
292
+ },
293
+ body: JSON.stringify({
294
+ licenseKey,
295
+ leaseId: this.leaseId
296
+ })
297
+ });
298
+ if (!response.ok) {
299
+ this.leaseId = void 0;
300
+ this.stopLeaseRenewal();
301
+ }
302
+ } catch {
303
+ }
304
+ },
305
+ 2 * 60 * 1e3
306
+ );
307
+ }
308
+ /**
309
+ * Stop lease renewal
310
+ */
311
+ stopLeaseRenewal() {
312
+ if (this.renewInterval) {
313
+ clearInterval(this.renewInterval);
314
+ this.renewInterval = void 0;
315
+ }
316
+ }
317
+ /**
318
+ * Check if we have an active lease
319
+ */
320
+ hasActiveLease() {
321
+ return !!this.leaseId;
322
+ }
323
+ /**
324
+ * Cleanup
325
+ */
326
+ async cleanup(licenseKey) {
327
+ await this.releaseLease(licenseKey);
328
+ }
329
+ }
330
+ function createSeatManager(serverUrl, apiKey) {
331
+ return new SeatManager(serverUrl, apiKey);
332
+ }
333
+ function createFloatingLicenseManager(serverUrl, apiKey) {
334
+ return new FloatingLicenseManager(serverUrl, apiKey);
335
+ }
336
+ function fnv1a(str) {
337
+ let hash = 2166136261;
338
+ for (let i = 0; i < str.length; i++) {
339
+ hash ^= str.charCodeAt(i);
340
+ hash = Math.imul(hash, 16777619);
341
+ }
342
+ return (hash >>> 0).toString(16).padStart(8, "0");
343
+ }
344
+ function hashLicenseKey(key) {
345
+ return fnv1a(key) + fnv1a(key + "nice2dev-salt");
346
+ }
347
+ class UsageTelemetry {
348
+ constructor(config) {
349
+ this.queue = [];
350
+ this.flushTimer = null;
351
+ this._active = false;
352
+ this.config = {
353
+ enabled: false,
354
+ batchSize: 20,
355
+ flushInterval: 6e4,
356
+ maxQueueSize: 500,
357
+ respectDNT: true,
358
+ apiKey: "",
359
+ ...config
360
+ };
361
+ }
362
+ /** Start collecting telemetry (no-op if disabled or DNT) */
363
+ start() {
364
+ if (!this.isAllowed()) {
365
+ return;
366
+ }
367
+ if (this._active) {
368
+ return;
369
+ }
370
+ this._active = true;
371
+ this.flushTimer = setInterval(() => this.flush(), this.config.flushInterval);
372
+ }
373
+ /** Stop collecting and flush remaining events */
374
+ async stop() {
375
+ if (!this._active) {
376
+ return;
377
+ }
378
+ this._active = false;
379
+ if (this.flushTimer) {
380
+ clearInterval(this.flushTimer);
381
+ this.flushTimer = null;
382
+ }
383
+ await this.flush();
384
+ }
385
+ /** Track a telemetry event */
386
+ track(type, data) {
387
+ if (!this._active) {
388
+ return;
389
+ }
390
+ const event = {
391
+ type,
392
+ licenseKeyHash: this.config.licenseKeyHash,
393
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
394
+ data
395
+ };
396
+ this.queue.push(event);
397
+ if (this.queue.length > this.config.maxQueueSize) {
398
+ this.queue = this.queue.slice(-this.config.maxQueueSize);
399
+ }
400
+ if (this.queue.length >= this.config.batchSize) {
401
+ this.flush();
402
+ }
403
+ }
404
+ /** Track feature usage */
405
+ trackFeatureUse(featureId) {
406
+ this.track("feature_use", { featureId });
407
+ }
408
+ /** Track activation event */
409
+ trackActivation() {
410
+ this.track("activation");
411
+ }
412
+ /** Track deactivation event */
413
+ trackDeactivation() {
414
+ this.track("deactivation");
415
+ }
416
+ /** Track validation event */
417
+ trackValidation(valid) {
418
+ this.track("validation", { valid });
419
+ }
420
+ /** Track error event */
421
+ trackError(errorCode) {
422
+ this.track("error", { errorCode });
423
+ }
424
+ /** Get queued event count */
425
+ get queueSize() {
426
+ return this.queue.length;
427
+ }
428
+ /** Check if telemetry is active */
429
+ get active() {
430
+ return this._active;
431
+ }
432
+ /** Flush queued events to server */
433
+ async flush() {
434
+ if (this.queue.length === 0) {
435
+ return;
436
+ }
437
+ const batch = this.queue.splice(0, this.config.batchSize);
438
+ try {
439
+ const headers = {
440
+ "Content-Type": "application/json"
441
+ };
442
+ if (this.config.apiKey) {
443
+ headers["X-API-Key"] = this.config.apiKey;
444
+ }
445
+ const response = await fetch(this.config.endpoint, {
446
+ method: "POST",
447
+ headers,
448
+ body: JSON.stringify({ events: batch })
449
+ });
450
+ if (!response.ok) {
451
+ this.queue.unshift(...batch);
452
+ }
453
+ } catch {
454
+ this.queue.unshift(...batch);
455
+ }
456
+ }
457
+ /** Enable telemetry (opt-in) */
458
+ enable() {
459
+ this.config.enabled = true;
460
+ this.start();
461
+ }
462
+ /** Disable telemetry (opt-out) */
463
+ disable() {
464
+ this.config.enabled = false;
465
+ this.stop();
466
+ }
467
+ /** Check if telemetry is allowed */
468
+ isAllowed() {
469
+ if (!this.config.enabled) {
470
+ return false;
471
+ }
472
+ if (this.config.respectDNT && typeof navigator !== "undefined" && navigator.doNotTrack === "1") {
473
+ return false;
474
+ }
475
+ return true;
476
+ }
477
+ }
478
+ function createTelemetry(config) {
479
+ return new UsageTelemetry(config);
480
+ }
481
+ const protectedModules = /* @__PURE__ */ new Map();
482
+ function registerProtectedModule(entry) {
483
+ protectedModules.set(entry.modulePath, entry);
484
+ }
485
+ function registerProtectedModules(entries) {
486
+ entries.forEach(registerProtectedModule);
487
+ }
488
+ function getAllProtectedModules() {
489
+ return Array.from(protectedModules.values());
490
+ }
491
+ function getProtectedModule(modulePath) {
492
+ return protectedModules.get(modulePath);
493
+ }
494
+ function isModuleAccessible(modulePath, currentTier) {
495
+ const entry = protectedModules.get(modulePath);
496
+ if (!entry) {
497
+ return true;
498
+ }
499
+ const tierRank2 = {
500
+ trial: 0,
501
+ personal: 1,
502
+ team: 2,
503
+ enterprise: 3,
504
+ site: 4,
505
+ oem: 5
506
+ };
507
+ return tierRank2[currentTier] >= tierRank2[entry.requiredTier];
508
+ }
509
+ function getInaccessibleModules(currentTier) {
510
+ return getAllProtectedModules().filter((m) => !isModuleAccessible(m.modulePath, currentTier));
511
+ }
512
+ function getAccessibleModules(currentTier) {
513
+ return getAllProtectedModules().filter((m) => isModuleAccessible(m.modulePath, currentTier));
514
+ }
515
+ function generateBundleTags() {
516
+ const byTier = /* @__PURE__ */ new Map();
517
+ for (const entry of protectedModules.values()) {
518
+ const key = `${entry.requiredTier}:${entry.accessLevel}`;
519
+ const group = byTier.get(key) ?? [];
520
+ group.push(entry);
521
+ byTier.set(key, group);
522
+ }
523
+ return Array.from(byTier.entries()).map(([key, entries]) => {
524
+ const [requiredTier, accessLevel] = key.split(":");
525
+ return {
526
+ chunkId: `protected-${requiredTier}-${accessLevel}`,
527
+ requiredTier,
528
+ accessLevel,
529
+ modules: entries.map((e) => e.modulePath)
530
+ };
531
+ });
532
+ }
533
+ function generateProtectionManifest() {
534
+ return {
535
+ version: "1.0.0",
536
+ generated: (/* @__PURE__ */ new Date()).toISOString(),
537
+ modules: getAllProtectedModules(),
538
+ bundleTags: generateBundleTags()
539
+ };
540
+ }
541
+ const DEFAULT_RATE_LIMITS = {
542
+ trial: { maxRequests: 30, windowMs: 6e4, burst: 5 },
543
+ personal: { maxRequests: 120, windowMs: 6e4, burst: 20 },
544
+ team: { maxRequests: 600, windowMs: 6e4, burst: 100 },
545
+ enterprise: { maxRequests: 3e3, windowMs: 6e4, burst: 500 },
546
+ site: { maxRequests: 1e4, windowMs: 6e4, burst: 2e3 },
547
+ oem: { maxRequests: 5e4, windowMs: 6e4, burst: 1e4 }
548
+ };
549
+ class RateLimiter {
550
+ constructor(config) {
551
+ this.config = config;
552
+ this.tokens = config.maxRequests + (config.burst ?? 0);
553
+ this.lastRefill = Date.now();
554
+ }
555
+ /** Refill tokens based on elapsed time */
556
+ refill() {
557
+ const now = Date.now();
558
+ const elapsed = now - this.lastRefill;
559
+ const maxTokens = this.config.maxRequests + (this.config.burst ?? 0);
560
+ const refillRate = this.config.maxRequests / this.config.windowMs;
561
+ const newTokens = elapsed * refillRate;
562
+ this.tokens = Math.min(maxTokens, this.tokens + newTokens);
563
+ this.lastRefill = now;
564
+ }
565
+ /** Check and consume one request token */
566
+ consume() {
567
+ this.refill();
568
+ const maxTokens = this.config.maxRequests + (this.config.burst ?? 0);
569
+ if (this.tokens >= 1) {
570
+ this.tokens -= 1;
571
+ return {
572
+ allowed: true,
573
+ remaining: Math.floor(this.tokens),
574
+ retryAfterMs: 0,
575
+ limit: maxTokens
576
+ };
577
+ }
578
+ const refillRate = this.config.maxRequests / this.config.windowMs;
579
+ const retryAfterMs = Math.ceil((1 - this.tokens) / refillRate);
580
+ return {
581
+ allowed: false,
582
+ remaining: 0,
583
+ retryAfterMs,
584
+ limit: maxTokens
585
+ };
586
+ }
587
+ /** Reset the limiter (e.g. after tier change) */
588
+ reset(config) {
589
+ if (config) {
590
+ this.config = config;
591
+ }
592
+ this.tokens = this.config.maxRequests + (this.config.burst ?? 0);
593
+ this.lastRefill = Date.now();
594
+ }
595
+ /** Peek at remaining tokens without consuming */
596
+ peek() {
597
+ this.refill();
598
+ const maxTokens = this.config.maxRequests + (this.config.burst ?? 0);
599
+ return {
600
+ allowed: this.tokens >= 1,
601
+ remaining: Math.floor(this.tokens),
602
+ retryAfterMs: 0,
603
+ limit: maxTokens
604
+ };
605
+ }
606
+ }
607
+ class ApiRateLimiter {
608
+ constructor(tier, config = DEFAULT_RATE_LIMITS) {
609
+ this.limiters = /* @__PURE__ */ new Map();
610
+ this.endpointOverrides = /* @__PURE__ */ new Map();
611
+ this.tier = tier;
612
+ this.config = config;
613
+ }
614
+ /** Set endpoint-specific rate limit overrides */
615
+ setEndpointOverride(endpoint, overrides) {
616
+ this.endpointOverrides.set(endpoint, overrides);
617
+ }
618
+ /** Update the current tier (resets all limiters) */
619
+ setTier(tier) {
620
+ this.tier = tier;
621
+ this.limiters.clear();
622
+ }
623
+ /** Get or create a limiter for an endpoint */
624
+ getLimiter(endpoint) {
625
+ let limiter = this.limiters.get(endpoint);
626
+ if (!limiter) {
627
+ const override = this.endpointOverrides.get(endpoint);
628
+ const tierConfig = (override && override[this.tier]) ?? this.config[this.tier];
629
+ limiter = new RateLimiter(tierConfig);
630
+ this.limiters.set(endpoint, limiter);
631
+ }
632
+ return limiter;
633
+ }
634
+ /** Check if a request to the given endpoint is allowed and consume a token */
635
+ check(endpoint = "default") {
636
+ return this.getLimiter(endpoint).consume();
637
+ }
638
+ /** Peek without consuming */
639
+ peek(endpoint = "default") {
640
+ return this.getLimiter(endpoint).peek();
641
+ }
642
+ /** Reset all limiters */
643
+ reset() {
644
+ this.limiters.clear();
645
+ }
646
+ }
647
+ function createApiRateLimiter(tier, config) {
648
+ return new ApiRateLimiter(tier, config);
649
+ }
650
+ const FEATURE_TIER_REQUIREMENTS = {
651
+ productName: "team",
652
+ logoUrl: "team",
653
+ logoDarkUrl: "team",
654
+ faviconUrl: "team",
655
+ primaryColor: "team",
656
+ secondaryColor: "team",
657
+ accentColor: "team",
658
+ customCss: "enterprise",
659
+ footerText: "enterprise",
660
+ hidePoweredBy: "enterprise",
661
+ customDomain: "site",
662
+ emailDomain: "site"
663
+ };
664
+ const tierRank = {
665
+ trial: 0,
666
+ personal: 1,
667
+ team: 2,
668
+ enterprise: 3,
669
+ site: 4,
670
+ oem: 5
671
+ };
672
+ class WhiteLabelManager {
673
+ constructor() {
674
+ this.config = {};
675
+ this.currentTier = "trial";
676
+ }
677
+ /** Set the active license tier */
678
+ setTier(tier) {
679
+ this.currentTier = tier;
680
+ }
681
+ /** Update white-label configuration (only applicable fields per tier are kept) */
682
+ setConfig(config) {
683
+ this.config = config;
684
+ }
685
+ /** Get the resolved configuration (filtered by tier permissions) */
686
+ getResolvedConfig() {
687
+ const resolved = {};
688
+ for (const [key, value] of Object.entries(this.config)) {
689
+ if (value === void 0) {
690
+ continue;
691
+ }
692
+ const requiredTier = FEATURE_TIER_REQUIREMENTS[key];
693
+ if (requiredTier && tierRank[this.currentTier] >= tierRank[requiredTier]) {
694
+ resolved[key] = value;
695
+ }
696
+ }
697
+ return resolved;
698
+ }
699
+ /** Check if a specific white-label feature is allowed */
700
+ isFeatureAllowed(feature) {
701
+ const requiredTier = FEATURE_TIER_REQUIREMENTS[feature];
702
+ return tierRank[this.currentTier] >= tierRank[requiredTier];
703
+ }
704
+ /** Get list of features not available at current tier */
705
+ getLockedFeatures() {
706
+ return Object.keys(FEATURE_TIER_REQUIREMENTS).filter(
707
+ (f) => !this.isFeatureAllowed(f)
708
+ );
709
+ }
710
+ /** Get list of features available at current tier */
711
+ getUnlockedFeatures() {
712
+ return Object.keys(FEATURE_TIER_REQUIREMENTS).filter(
713
+ (f) => this.isFeatureAllowed(f)
714
+ );
715
+ }
716
+ /** Generate CSS custom properties from the resolved config */
717
+ generateCssVariables() {
718
+ const cfg = this.getResolvedConfig();
719
+ const vars = [];
720
+ if (cfg.primaryColor) {
721
+ vars.push(`--nice-brand-primary: ${cfg.primaryColor};`);
722
+ }
723
+ if (cfg.secondaryColor) {
724
+ vars.push(`--nice-brand-secondary: ${cfg.secondaryColor};`);
725
+ }
726
+ if (cfg.accentColor) {
727
+ vars.push(`--nice-brand-accent: ${cfg.accentColor};`);
728
+ }
729
+ if (cfg.logoUrl) {
730
+ vars.push(`--nice-brand-logo: url(${cfg.logoUrl});`);
731
+ }
732
+ if (cfg.logoDarkUrl) {
733
+ vars.push(`--nice-brand-logo-dark: url(${cfg.logoDarkUrl});`);
734
+ }
735
+ if (vars.length === 0) {
736
+ return "";
737
+ }
738
+ return `:root {
739
+ ${vars.join("\n ")}
740
+ }`;
741
+ }
742
+ /** Apply the brand configuration to the document */
743
+ applyToDocument() {
744
+ if (typeof document === "undefined") {
745
+ return;
746
+ }
747
+ const cfg = this.getResolvedConfig();
748
+ const css = this.generateCssVariables();
749
+ if (css) {
750
+ let styleEl = document.getElementById("nice-whitelabel-styles");
751
+ if (!styleEl) {
752
+ styleEl = document.createElement("style");
753
+ styleEl.id = "nice-whitelabel-styles";
754
+ document.head.appendChild(styleEl);
755
+ }
756
+ styleEl.textContent = css + (cfg.customCss ? `
757
+ ${cfg.customCss}` : "");
758
+ }
759
+ if (cfg.faviconUrl) {
760
+ let link = document.querySelector('link[rel="icon"]');
761
+ if (!link) {
762
+ link = document.createElement("link");
763
+ link.rel = "icon";
764
+ document.head.appendChild(link);
765
+ }
766
+ link.href = cfg.faviconUrl;
767
+ }
768
+ if (cfg.productName) {
769
+ document.title = cfg.productName;
770
+ }
771
+ }
772
+ }
773
+ function createWhiteLabelManager(tier, config) {
774
+ const mgr = new WhiteLabelManager();
775
+ mgr.setTier(tier);
776
+ if (config) {
777
+ mgr.setConfig(config);
778
+ }
779
+ return mgr;
780
+ }
781
+ const DEFAULT_SLA_CONFIGS = {
782
+ trial: {
783
+ uptimePercent: 95,
784
+ supportResponseHours: 72,
785
+ supportResolutionHours: 168,
786
+ supportChannels: ["email"],
787
+ priority: 6,
788
+ includedHours: 0,
789
+ breachPenaltyPercent: 0
790
+ },
791
+ personal: {
792
+ uptimePercent: 99,
793
+ supportResponseHours: 48,
794
+ supportResolutionHours: 120,
795
+ supportChannels: ["email"],
796
+ priority: 5,
797
+ includedHours: 2,
798
+ breachPenaltyPercent: 0
799
+ },
800
+ team: {
801
+ uptimePercent: 99.5,
802
+ supportResponseHours: 24,
803
+ supportResolutionHours: 72,
804
+ supportChannels: ["email", "chat"],
805
+ priority: 4,
806
+ includedHours: 10,
807
+ breachPenaltyPercent: 5
808
+ },
809
+ enterprise: {
810
+ uptimePercent: 99.9,
811
+ supportResponseHours: 4,
812
+ supportResolutionHours: 24,
813
+ supportChannels: ["email", "chat", "phone"],
814
+ priority: 3,
815
+ includedHours: 40,
816
+ breachPenaltyPercent: 10
817
+ },
818
+ site: {
819
+ uptimePercent: 99.95,
820
+ supportResponseHours: 2,
821
+ supportResolutionHours: 12,
822
+ supportChannels: ["email", "chat", "phone", "dedicated-manager"],
823
+ priority: 2,
824
+ includedHours: null,
825
+ breachPenaltyPercent: 15
826
+ },
827
+ oem: {
828
+ uptimePercent: 99.99,
829
+ supportResponseHours: 1,
830
+ supportResolutionHours: 4,
831
+ supportChannels: ["email", "chat", "phone", "dedicated-manager", "on-site"],
832
+ priority: 1,
833
+ includedHours: null,
834
+ breachPenaltyPercent: 20
835
+ }
836
+ };
837
+ class SlaTracker {
838
+ constructor(tier, config = DEFAULT_SLA_CONFIGS) {
839
+ this.events = [];
840
+ this.tier = tier;
841
+ this.config = config;
842
+ }
843
+ /** Update the license tier */
844
+ setTier(tier) {
845
+ this.tier = tier;
846
+ }
847
+ /** Get the SLA config for the current tier */
848
+ getSlaConfig() {
849
+ return this.config[this.tier];
850
+ }
851
+ /** Record an event */
852
+ recordEvent(event) {
853
+ this.events.push(event);
854
+ }
855
+ /** Record an uptime check */
856
+ recordUptimeCheck() {
857
+ var _a;
858
+ this.recordEvent({
859
+ id: ((_a = crypto.randomUUID) == null ? void 0 : _a.call(crypto)) ?? Date.now().toString(36),
860
+ type: "uptime-check",
861
+ timestamp: (/* @__PURE__ */ new Date()).toISOString()
862
+ });
863
+ }
864
+ /** Record downtime */
865
+ recordDowntime(durationMs) {
866
+ var _a, _b;
867
+ const now = /* @__PURE__ */ new Date();
868
+ this.recordEvent({
869
+ id: ((_a = crypto.randomUUID) == null ? void 0 : _a.call(crypto)) ?? Date.now().toString(36),
870
+ type: "downtime-start",
871
+ timestamp: new Date(now.getTime() - durationMs).toISOString(),
872
+ durationMs
873
+ });
874
+ this.recordEvent({
875
+ id: ((_b = crypto.randomUUID) == null ? void 0 : _b.call(crypto)) ?? (Date.now() + 1).toString(36),
876
+ type: "downtime-end",
877
+ timestamp: now.toISOString()
878
+ });
879
+ }
880
+ /** Record a support ticket lifecycle */
881
+ recordTicket(responseTimeHours, resolutionTimeHours) {
882
+ var _a, _b;
883
+ const now = /* @__PURE__ */ new Date();
884
+ this.recordEvent({
885
+ id: ((_a = crypto.randomUUID) == null ? void 0 : _a.call(crypto)) ?? Date.now().toString(36),
886
+ type: "ticket-opened",
887
+ timestamp: now.toISOString(),
888
+ metadata: { responseTimeHours, resolutionTimeHours }
889
+ });
890
+ this.recordEvent({
891
+ id: ((_b = crypto.randomUUID) == null ? void 0 : _b.call(crypto)) ?? (Date.now() + 1).toString(36),
892
+ type: "ticket-resolved",
893
+ timestamp: new Date(now.getTime() + resolutionTimeHours * 36e5).toISOString(),
894
+ durationMs: resolutionTimeHours * 36e5,
895
+ metadata: { responseTimeHours }
896
+ });
897
+ }
898
+ /** Generate an SLA report for a given period */
899
+ generateReport(periodStart, periodEnd) {
900
+ const slaConfig = this.getSlaConfig();
901
+ const periodMs = periodEnd.getTime() - periodStart.getTime();
902
+ const periodEvents = this.events.filter((e) => {
903
+ const t = new Date(e.timestamp).getTime();
904
+ return t >= periodStart.getTime() && t <= periodEnd.getTime();
905
+ });
906
+ const downtimeEvents = periodEvents.filter((e) => e.type === "downtime-start");
907
+ const totalDowntimeMs = downtimeEvents.reduce((sum, e) => sum + (e.durationMs ?? 0), 0);
908
+ const actualUptimePercent = periodMs > 0 ? (periodMs - totalDowntimeMs) / periodMs * 100 : 100;
909
+ const tickets = periodEvents.filter((e) => e.type === "ticket-resolved");
910
+ const responseTimes = tickets.map((t) => {
911
+ var _a;
912
+ return ((_a = t.metadata) == null ? void 0 : _a.responseTimeHours) ?? 0;
913
+ }).filter((h) => h > 0);
914
+ const resolutionTimes = tickets.map((t) => (t.durationMs ?? 0) / 36e5).filter((h) => h > 0);
915
+ const avgResponseHours = responseTimes.length > 0 ? responseTimes.reduce((a, b) => a + b, 0) / responseTimes.length : 0;
916
+ const avgResolutionHours = resolutionTimes.length > 0 ? resolutionTimes.reduce((a, b) => a + b, 0) / resolutionTimes.length : 0;
917
+ let breachCount = 0;
918
+ if (actualUptimePercent < slaConfig.uptimePercent) {
919
+ breachCount++;
920
+ }
921
+ if (avgResponseHours > slaConfig.supportResponseHours && responseTimes.length > 0) {
922
+ breachCount++;
923
+ }
924
+ if (avgResolutionHours > slaConfig.supportResolutionHours && resolutionTimes.length > 0) {
925
+ breachCount++;
926
+ }
927
+ const compliant = breachCount === 0;
928
+ const creditPercent = compliant ? 0 : breachCount * slaConfig.breachPenaltyPercent;
929
+ return {
930
+ periodStart: periodStart.toISOString(),
931
+ periodEnd: periodEnd.toISOString(),
932
+ tier: this.tier,
933
+ slaConfig,
934
+ actualUptimePercent: Math.round(actualUptimePercent * 100) / 100,
935
+ totalDowntimeMs,
936
+ avgResponseHours: Math.round(avgResponseHours * 10) / 10,
937
+ avgResolutionHours: Math.round(avgResolutionHours * 10) / 10,
938
+ breachCount,
939
+ compliant,
940
+ creditPercent: Math.min(100, creditPercent)
941
+ };
942
+ }
943
+ /** Get all recorded events */
944
+ getEvents() {
945
+ return [...this.events];
946
+ }
947
+ /** Clear all events */
948
+ clearEvents() {
949
+ this.events = [];
950
+ }
951
+ }
952
+ function createSlaTracker(tier) {
953
+ return new SlaTracker(tier);
954
+ }
955
+ function auditHash(str) {
956
+ let hash = 2166136261;
957
+ for (let i = 0; i < str.length; i++) {
958
+ hash ^= str.charCodeAt(i);
959
+ hash = Math.imul(hash, 16777619);
960
+ }
961
+ return (hash >>> 0).toString(16).padStart(8, "0");
962
+ }
963
+ function hashKeyForAudit(key) {
964
+ return auditHash(key) + auditHash(key + "audit-salt");
965
+ }
966
+ class AuditTrail {
967
+ constructor(options) {
968
+ this.entries = [];
969
+ this.serverUrl = options == null ? void 0 : options.serverUrl;
970
+ this.apiKey = options == null ? void 0 : options.apiKey;
971
+ }
972
+ /** Append an audit entry */
973
+ log(event, licenseKey, actor, description, details) {
974
+ var _a;
975
+ const entry = {
976
+ id: ((_a = crypto.randomUUID) == null ? void 0 : _a.call(crypto)) ?? `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`,
977
+ event,
978
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
979
+ licenseKeyHash: hashKeyForAudit(licenseKey),
980
+ actor,
981
+ description,
982
+ previousValue: details == null ? void 0 : details.previousValue,
983
+ newValue: details == null ? void 0 : details.newValue,
984
+ ipHash: details == null ? void 0 : details.ipHash,
985
+ metadata: details == null ? void 0 : details.metadata
986
+ };
987
+ this.entries.push(entry);
988
+ if (this.serverUrl) {
989
+ this.sendToServer(entry).catch(() => {
990
+ });
991
+ }
992
+ return entry;
993
+ }
994
+ // ─── Convenience logging methods ────────────────────────────
995
+ logActivation(licenseKey, actor) {
996
+ return this.log("license.activated", licenseKey, actor, "License activated");
997
+ }
998
+ logDeactivation(licenseKey, actor) {
999
+ return this.log("license.deactivated", licenseKey, actor, "License deactivated");
1000
+ }
1001
+ logTierChange(licenseKey, actor, from, to) {
1002
+ return this.log(
1003
+ "license.tier-changed",
1004
+ licenseKey,
1005
+ actor,
1006
+ `Tier changed from ${from} to ${to}`,
1007
+ { previousValue: from, newValue: to }
1008
+ );
1009
+ }
1010
+ logSeatsChange(licenseKey, actor, from, to) {
1011
+ return this.log(
1012
+ "license.seats-changed",
1013
+ licenseKey,
1014
+ actor,
1015
+ `Seats changed from ${from} to ${to}`,
1016
+ { previousValue: from, newValue: to }
1017
+ );
1018
+ }
1019
+ logRevocation(licenseKey, actor, reason) {
1020
+ return this.log(
1021
+ "license.revoked",
1022
+ licenseKey,
1023
+ actor,
1024
+ `License revoked${reason ? `: ${reason}` : ""}`,
1025
+ {
1026
+ metadata: reason ? { reason } : void 0
1027
+ }
1028
+ );
1029
+ }
1030
+ // ─── Query ──────────────────────────────────────────────────
1031
+ /** Query audit entries with filters */
1032
+ query(filter = {}) {
1033
+ var _a;
1034
+ let results = [...this.entries];
1035
+ if ((_a = filter.events) == null ? void 0 : _a.length) {
1036
+ const set = new Set(filter.events);
1037
+ results = results.filter((e) => set.has(e.event));
1038
+ }
1039
+ if (filter.licenseKeyHash) {
1040
+ results = results.filter((e) => e.licenseKeyHash === filter.licenseKeyHash);
1041
+ }
1042
+ if (filter.actorId) {
1043
+ results = results.filter((e) => e.actor.id === filter.actorId);
1044
+ }
1045
+ if (filter.from) {
1046
+ const from = filter.from.getTime();
1047
+ results = results.filter((e) => new Date(e.timestamp).getTime() >= from);
1048
+ }
1049
+ if (filter.to) {
1050
+ const to = filter.to.getTime();
1051
+ results = results.filter((e) => new Date(e.timestamp).getTime() <= to);
1052
+ }
1053
+ results.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
1054
+ if (filter.offset) {
1055
+ results = results.slice(filter.offset);
1056
+ }
1057
+ if (filter.limit) {
1058
+ results = results.slice(0, filter.limit);
1059
+ }
1060
+ return results;
1061
+ }
1062
+ /** Get total entry count (optionally filtered) */
1063
+ count(filter) {
1064
+ if (!filter) {
1065
+ return this.entries.length;
1066
+ }
1067
+ return this.query({ ...filter, limit: void 0, offset: void 0 }).length;
1068
+ }
1069
+ // ─── Export ─────────────────────────────────────────────────
1070
+ /** Export audit entries */
1071
+ export(filter, format = "json") {
1072
+ const entries = this.query(filter ?? {});
1073
+ if (format === "csv") {
1074
+ const header = "id,event,timestamp,licenseKeyHash,actorType,actorId,description";
1075
+ const rows = entries.map(
1076
+ (e) => `"${e.id}","${e.event}","${e.timestamp}","${e.licenseKeyHash}","${e.actor.type}","${e.actor.id}","${e.description.replace(/"/g, '""')}"`
1077
+ );
1078
+ return [header, ...rows].join("\n");
1079
+ }
1080
+ return JSON.stringify(entries, null, 2);
1081
+ }
1082
+ // ─── Server sync ────────────────────────────────────────────
1083
+ async sendToServer(entry) {
1084
+ if (!this.serverUrl) {
1085
+ return;
1086
+ }
1087
+ const headers = { "Content-Type": "application/json" };
1088
+ if (this.apiKey) {
1089
+ headers["X-API-Key"] = this.apiKey;
1090
+ }
1091
+ await fetch(`${this.serverUrl}/audit`, {
1092
+ method: "POST",
1093
+ headers,
1094
+ body: JSON.stringify(entry)
1095
+ });
1096
+ }
1097
+ /** Get all entries (read-only copy) */
1098
+ getAll() {
1099
+ return [...this.entries];
1100
+ }
1101
+ }
1102
+ function createAuditTrail(options) {
1103
+ return new AuditTrail(options);
1104
+ }
1105
+ class LicensePortalService {
1106
+ constructor(config) {
1107
+ this.config = config;
1108
+ }
1109
+ async request(endpoint, options = {}) {
1110
+ const url = `${this.config.apiBaseUrl}${endpoint}`;
1111
+ const headers = {
1112
+ "Content-Type": "application/json",
1113
+ ...this.config.authToken && {
1114
+ Authorization: `Bearer ${this.config.authToken}`
1115
+ }
1116
+ };
1117
+ const response = await fetch(url, {
1118
+ ...options,
1119
+ headers: { ...headers, ...options.headers }
1120
+ });
1121
+ if (!response.ok) {
1122
+ const error = await response.json().catch(() => ({}));
1123
+ throw {
1124
+ code: `HTTP_${response.status}`,
1125
+ message: error.message || response.statusText,
1126
+ details: error
1127
+ };
1128
+ }
1129
+ return response.json();
1130
+ }
1131
+ /** Get customer license overview */
1132
+ async getOverview() {
1133
+ return this.request(
1134
+ `/customers/${this.config.customerId}/licenses/overview`
1135
+ );
1136
+ }
1137
+ /** Get detailed license info */
1138
+ async getLicense(licenseKey) {
1139
+ return this.request(`/licenses/${encodeURIComponent(licenseKey)}`);
1140
+ }
1141
+ /** Activate license on current machine */
1142
+ async activateLicense(licenseKey, machineId, machineName) {
1143
+ return this.request(`/licenses/${encodeURIComponent(licenseKey)}/activate`, {
1144
+ method: "POST",
1145
+ body: JSON.stringify({ machineId, machineName })
1146
+ });
1147
+ }
1148
+ /** Deactivate license from machine */
1149
+ async deactivateLicense(licenseKey, machineId) {
1150
+ await this.request(`/licenses/${encodeURIComponent(licenseKey)}/deactivate`, {
1151
+ method: "POST",
1152
+ body: JSON.stringify({ machineId })
1153
+ });
1154
+ }
1155
+ /** Transfer license to another user/org */
1156
+ async transferLicense(licenseKey, targetEmail, notes) {
1157
+ return this.request(`/licenses/${encodeURIComponent(licenseKey)}/transfer`, {
1158
+ method: "POST",
1159
+ body: JSON.stringify({ targetEmail, notes })
1160
+ });
1161
+ }
1162
+ /** Get seat assignments */
1163
+ async getSeats(licenseKey) {
1164
+ return this.request(`/licenses/${encodeURIComponent(licenseKey)}/seats`);
1165
+ }
1166
+ /** Assign seat to user */
1167
+ async assignSeat(licenseKey, userId, email) {
1168
+ return this.request(`/licenses/${encodeURIComponent(licenseKey)}/seats`, {
1169
+ method: "POST",
1170
+ body: JSON.stringify({ userId, email })
1171
+ });
1172
+ }
1173
+ /** Remove seat assignment */
1174
+ async removeSeat(licenseKey, seatId) {
1175
+ await this.request(`/licenses/${encodeURIComponent(licenseKey)}/seats/${seatId}`, {
1176
+ method: "DELETE"
1177
+ });
1178
+ }
1179
+ /** Get subscription info */
1180
+ async getSubscription() {
1181
+ try {
1182
+ return await this.request(
1183
+ `/customers/${this.config.customerId}/subscription`
1184
+ );
1185
+ } catch {
1186
+ return null;
1187
+ }
1188
+ }
1189
+ /** Get invoices */
1190
+ async getInvoices(limit = 10, offset = 0) {
1191
+ return this.request(
1192
+ `/customers/${this.config.customerId}/invoices?limit=${limit}&offset=${offset}`
1193
+ );
1194
+ }
1195
+ /** Get action history */
1196
+ async getActionHistory(licenseKey, limit = 50) {
1197
+ const params = new URLSearchParams({ limit: String(limit) });
1198
+ if (licenseKey) {
1199
+ params.set("licenseKey", licenseKey);
1200
+ }
1201
+ return this.request(`/customers/${this.config.customerId}/actions?${params}`);
1202
+ }
1203
+ /** Request license renewal */
1204
+ async requestRenewal(licenseKey) {
1205
+ return this.request(`/licenses/${encodeURIComponent(licenseKey)}/renew`, {
1206
+ method: "POST"
1207
+ });
1208
+ }
1209
+ /** Request tier upgrade */
1210
+ async requestUpgrade(licenseKey, targetTier) {
1211
+ return this.request(`/licenses/${encodeURIComponent(licenseKey)}/upgrade`, {
1212
+ method: "POST",
1213
+ body: JSON.stringify({ targetTier })
1214
+ });
1215
+ }
1216
+ }
1217
+ function getStatusColor(status) {
1218
+ const colors = {
1219
+ valid: "#22c55e",
1220
+ expired: "#ef4444",
1221
+ revoked: "#a855f7",
1222
+ invalid: "#f97316",
1223
+ grace: "#eab308",
1224
+ pending: "#6b7280"
1225
+ };
1226
+ return colors[status] || "#6b7280";
1227
+ }
1228
+ function getTierLabel(tier) {
1229
+ const labels = {
1230
+ trial: "Trial",
1231
+ personal: "Personal",
1232
+ team: "Team",
1233
+ enterprise: "Enterprise",
1234
+ site: "Site License",
1235
+ oem: "OEM"
1236
+ };
1237
+ return labels[tier] || tier;
1238
+ }
1239
+ function formatDate(isoDate, locale = "en-US") {
1240
+ return new Date(isoDate).toLocaleDateString(locale, {
1241
+ year: "numeric",
1242
+ month: "short",
1243
+ day: "numeric"
1244
+ });
1245
+ }
1246
+ function getDaysUntilExpiration(expiresAt) {
1247
+ if (!expiresAt) {
1248
+ return null;
1249
+ }
1250
+ const now = /* @__PURE__ */ new Date();
1251
+ const expiry = new Date(expiresAt);
1252
+ const diff = expiry.getTime() - now.getTime();
1253
+ return Math.ceil(diff / (1e3 * 60 * 60 * 24));
1254
+ }
1255
+ function useLicensePortal(config) {
1256
+ const [state, setState] = React.useState({
1257
+ loading: true,
1258
+ error: null,
1259
+ overview: null,
1260
+ selectedLicense: null,
1261
+ actionHistory: [],
1262
+ subscription: null,
1263
+ invoices: [],
1264
+ activeTab: "overview"
1265
+ });
1266
+ const service = React.useMemo(
1267
+ () => new LicensePortalService(config),
1268
+ [config.apiBaseUrl, config.authToken, config.customerId]
1269
+ );
1270
+ const loadData = React.useCallback(async () => {
1271
+ var _a;
1272
+ setState((s) => ({ ...s, loading: true, error: null }));
1273
+ try {
1274
+ const [overview, subscription, actionsData, invoicesData] = await Promise.all([
1275
+ service.getOverview(),
1276
+ config.showBilling ? service.getSubscription() : null,
1277
+ service.getActionHistory(),
1278
+ config.showBilling ? service.getInvoices() : { invoices: [], total: 0 }
1279
+ ]);
1280
+ setState((s) => ({
1281
+ ...s,
1282
+ loading: false,
1283
+ overview,
1284
+ subscription,
1285
+ actionHistory: actionsData,
1286
+ invoices: invoicesData.invoices
1287
+ }));
1288
+ } catch (error) {
1289
+ const licenseError = error;
1290
+ setState((s) => ({ ...s, loading: false, error: licenseError }));
1291
+ (_a = config.onError) == null ? void 0 : _a.call(config, licenseError);
1292
+ }
1293
+ }, [service, config]);
1294
+ React.useEffect(() => {
1295
+ loadData();
1296
+ }, [loadData]);
1297
+ const activateLicense = React.useCallback(
1298
+ async (licenseKey, machineId, machineName) => {
1299
+ var _a, _b;
1300
+ try {
1301
+ const license = await service.activateLicense(licenseKey, machineId, machineName);
1302
+ (_a = config.onLicenseActivated) == null ? void 0 : _a.call(config, license);
1303
+ await loadData();
1304
+ return license;
1305
+ } catch (error) {
1306
+ (_b = config.onError) == null ? void 0 : _b.call(config, error);
1307
+ throw error;
1308
+ }
1309
+ },
1310
+ [service, config, loadData]
1311
+ );
1312
+ const deactivateLicense = React.useCallback(
1313
+ async (licenseKey, machineId) => {
1314
+ var _a, _b;
1315
+ try {
1316
+ await service.deactivateLicense(licenseKey, machineId);
1317
+ (_a = config.onLicenseDeactivated) == null ? void 0 : _a.call(config, licenseKey);
1318
+ await loadData();
1319
+ } catch (error) {
1320
+ (_b = config.onError) == null ? void 0 : _b.call(config, error);
1321
+ throw error;
1322
+ }
1323
+ },
1324
+ [service, config, loadData]
1325
+ );
1326
+ const transferLicense = React.useCallback(
1327
+ async (licenseKey, targetEmail, notes) => {
1328
+ var _a;
1329
+ try {
1330
+ const result = await service.transferLicense(licenseKey, targetEmail, notes);
1331
+ await loadData();
1332
+ return result;
1333
+ } catch (error) {
1334
+ (_a = config.onError) == null ? void 0 : _a.call(config, error);
1335
+ throw error;
1336
+ }
1337
+ },
1338
+ [service, config, loadData]
1339
+ );
1340
+ const selectLicense = React.useCallback((license) => {
1341
+ setState((s) => ({ ...s, selectedLicense: license }));
1342
+ }, []);
1343
+ const setActiveTab = React.useCallback((tab) => {
1344
+ setState((s) => ({ ...s, activeTab: tab }));
1345
+ }, []);
1346
+ return {
1347
+ state,
1348
+ service,
1349
+ loadData,
1350
+ activateLicense,
1351
+ deactivateLicense,
1352
+ transferLicense,
1353
+ selectLicense,
1354
+ setActiveTab
1355
+ };
1356
+ }
1357
+ const LicenseStatusBadge = ({ status, size = "md" }) => {
1358
+ const sizeClasses = {
1359
+ sm: "px-2 py-0.5 text-xs",
1360
+ md: "px-2.5 py-1 text-sm",
1361
+ lg: "px-3 py-1.5 text-base"
1362
+ };
1363
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1364
+ "span",
1365
+ {
1366
+ className: `inline-flex items-center rounded-full font-medium ${sizeClasses[size]}`,
1367
+ style: {
1368
+ backgroundColor: `${getStatusColor(status)}20`,
1369
+ color: getStatusColor(status)
1370
+ },
1371
+ children: [
1372
+ /* @__PURE__ */ jsxRuntime.jsx(
1373
+ "span",
1374
+ {
1375
+ className: "mr-1.5 h-2 w-2 rounded-full",
1376
+ style: { backgroundColor: getStatusColor(status) }
1377
+ }
1378
+ ),
1379
+ status.charAt(0).toUpperCase() + status.slice(1)
1380
+ ]
1381
+ }
1382
+ );
1383
+ };
1384
+ const LicenseTierBadge = ({ tier, size = "md" }) => {
1385
+ const tierColors = {
1386
+ trial: "#6b7280",
1387
+ personal: "#3b82f6",
1388
+ team: "#8b5cf6",
1389
+ enterprise: "#f59e0b",
1390
+ site: "#10b981",
1391
+ oem: "#ec4899"
1392
+ };
1393
+ const sizeClasses = {
1394
+ sm: "px-2 py-0.5 text-xs",
1395
+ md: "px-2.5 py-1 text-sm",
1396
+ lg: "px-3 py-1.5 text-base"
1397
+ };
1398
+ return /* @__PURE__ */ jsxRuntime.jsx(
1399
+ "span",
1400
+ {
1401
+ className: `inline-flex items-center rounded font-semibold ${sizeClasses[size]}`,
1402
+ style: {
1403
+ backgroundColor: `${tierColors[tier]}20`,
1404
+ color: tierColors[tier],
1405
+ border: `1px solid ${tierColors[tier]}40`
1406
+ },
1407
+ children: getTierLabel(tier)
1408
+ }
1409
+ );
1410
+ };
1411
+ const LicenseCard = ({ license, onSelect, onActivate, onDeactivate, selected }) => {
1412
+ const daysRemaining = getDaysUntilExpiration(license.expiresAt);
1413
+ const isExpiringSoon = daysRemaining !== null && daysRemaining <= 30 && daysRemaining > 0;
1414
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1415
+ "div",
1416
+ {
1417
+ className: `rounded-lg border p-4 transition-all cursor-pointer hover:shadow-md ${selected ? "border-blue-500 ring-2 ring-blue-200" : "border-gray-200"}`,
1418
+ onClick: onSelect,
1419
+ children: [
1420
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between mb-3", children: [
1421
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1422
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-mono text-sm text-gray-500 mb-1", children: license.key }),
1423
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
1424
+ /* @__PURE__ */ jsxRuntime.jsx(LicenseTierBadge, { tier: license.tier, size: "sm" }),
1425
+ /* @__PURE__ */ jsxRuntime.jsx(LicenseStatusBadge, { status: license.status, size: "sm" })
1426
+ ] })
1427
+ ] }),
1428
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-right text-sm", children: [
1429
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-gray-500", children: "Seats" }),
1430
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "font-semibold", children: [
1431
+ license.activeSeats,
1432
+ "/",
1433
+ license.maxSeats ?? "∞"
1434
+ ] })
1435
+ ] })
1436
+ ] }),
1437
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-sm text-gray-600 mb-3", children: [
1438
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1439
+ "Licensee: ",
1440
+ license.licensee
1441
+ ] }),
1442
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1443
+ "Email: ",
1444
+ license.email
1445
+ ] })
1446
+ ] }),
1447
+ license.expiresAt && /* @__PURE__ */ jsxRuntime.jsx("div", { className: `text-sm ${isExpiringSoon ? "text-orange-600" : "text-gray-500"}`, children: daysRemaining !== null && daysRemaining > 0 ? /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
1448
+ "Expires in ",
1449
+ daysRemaining,
1450
+ " days (",
1451
+ formatDate(license.expiresAt),
1452
+ ")"
1453
+ ] }) : daysRemaining === 0 ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-red-600", children: "Expires today" }) : /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-red-600", children: [
1454
+ "Expired ",
1455
+ formatDate(license.expiresAt)
1456
+ ] }) }),
1457
+ (onActivate || onDeactivate) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2 mt-3 pt-3 border-t", children: [
1458
+ onActivate && license.status === "valid" && /* @__PURE__ */ jsxRuntime.jsx(
1459
+ "button",
1460
+ {
1461
+ className: "px-3 py-1.5 text-sm bg-blue-500 text-white rounded hover:bg-blue-600",
1462
+ onClick: (e) => {
1463
+ e.stopPropagation();
1464
+ onActivate();
1465
+ },
1466
+ children: "Activate"
1467
+ }
1468
+ ),
1469
+ onDeactivate && /* @__PURE__ */ jsxRuntime.jsx(
1470
+ "button",
1471
+ {
1472
+ className: "px-3 py-1.5 text-sm bg-gray-200 text-gray-700 rounded hover:bg-gray-300",
1473
+ onClick: (e) => {
1474
+ e.stopPropagation();
1475
+ onDeactivate();
1476
+ },
1477
+ children: "Deactivate"
1478
+ }
1479
+ )
1480
+ ] })
1481
+ ]
1482
+ }
1483
+ );
1484
+ };
1485
+ const LicensePortal = (props) => {
1486
+ const { className, style, ...config } = props;
1487
+ const { state, activateLicense, deactivateLicense, selectLicense, setActiveTab, loadData } = useLicensePortal(config);
1488
+ if (state.loading) {
1489
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `nice-license-portal ${className || ""}`, style, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-center p-8", children: [
1490
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-spin rounded-full h-8 w-8 border-b-2 border-blue-500" }),
1491
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-3 text-gray-600", children: "Loading licenses..." })
1492
+ ] }) });
1493
+ }
1494
+ if (state.error) {
1495
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: `nice-license-portal ${className || ""}`, style, children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 bg-red-50 border border-red-200 rounded-lg", children: [
1496
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-red-800 font-semibold", children: "Error loading licenses" }),
1497
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-600 text-sm mt-1", children: state.error.message }),
1498
+ /* @__PURE__ */ jsxRuntime.jsx(
1499
+ "button",
1500
+ {
1501
+ className: "mt-3 px-4 py-2 bg-red-100 text-red-700 rounded hover:bg-red-200",
1502
+ onClick: loadData,
1503
+ children: "Retry"
1504
+ }
1505
+ )
1506
+ ] }) });
1507
+ }
1508
+ const { overview } = state;
1509
+ if (!overview) {
1510
+ return null;
1511
+ }
1512
+ const tabs = [
1513
+ { id: "overview", label: "Overview" },
1514
+ { id: "licenses", label: "Licenses" },
1515
+ { id: "seats", label: "Seat Management" },
1516
+ ...config.showBilling ? [{ id: "billing", label: "Billing" }] : [],
1517
+ ...config.showAnalytics ? [{ id: "analytics", label: "Analytics" }] : [],
1518
+ { id: "settings", label: "Settings" }
1519
+ ];
1520
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `nice-license-portal ${className || ""}`, style, children: [
1521
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6", children: [
1522
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl font-bold text-gray-900", children: "License Portal" }),
1523
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600", children: "Manage your Nice2Dev licenses and subscriptions" })
1524
+ ] }),
1525
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "border-b border-gray-200 mb-6", children: /* @__PURE__ */ jsxRuntime.jsx("nav", { className: "flex gap-6", children: tabs.map((tab) => /* @__PURE__ */ jsxRuntime.jsx(
1526
+ "button",
1527
+ {
1528
+ className: `pb-3 text-sm font-medium border-b-2 transition-colors ${state.activeTab === tab.id ? "border-blue-500 text-blue-600" : "border-transparent text-gray-500 hover:text-gray-700"}`,
1529
+ onClick: () => setActiveTab(tab.id),
1530
+ children: tab.label
1531
+ },
1532
+ tab.id
1533
+ )) }) }),
1534
+ state.activeTab === "overview" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-6", children: [
1535
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 md:grid-cols-4 gap-4", children: [
1536
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white p-4 rounded-lg border border-gray-200", children: [
1537
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm text-gray-500", children: "Total Licenses" }),
1538
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-2xl font-bold", children: overview.totalLicenses })
1539
+ ] }),
1540
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white p-4 rounded-lg border border-gray-200", children: [
1541
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm text-gray-500", children: "Active" }),
1542
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-2xl font-bold text-green-600", children: overview.activeLicenses })
1543
+ ] }),
1544
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white p-4 rounded-lg border border-gray-200", children: [
1545
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm text-gray-500", children: "Seats Used" }),
1546
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-2xl font-bold", children: [
1547
+ overview.usedSeats,
1548
+ "/",
1549
+ overview.totalSeats
1550
+ ] })
1551
+ ] }),
1552
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white p-4 rounded-lg border border-gray-200", children: [
1553
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm text-gray-500", children: "Expiring Soon" }),
1554
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-2xl font-bold text-orange-600", children: overview.expiringSoon.length })
1555
+ ] })
1556
+ ] }),
1557
+ overview.expiringSoon.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-orange-50 border border-orange-200 rounded-lg p-4", children: [
1558
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-semibold text-orange-800 mb-2", children: "Licenses Expiring Soon" }),
1559
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-2", children: overview.expiringSoon.map((license) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-sm", children: [
1560
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono text-orange-700", children: license.key }),
1561
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-orange-600", children: [
1562
+ getDaysUntilExpiration(license.expiresAt),
1563
+ " days remaining"
1564
+ ] })
1565
+ ] }, license.key)) })
1566
+ ] }),
1567
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white rounded-lg border border-gray-200", children: [
1568
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 border-b border-gray-200", children: /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-semibold", children: "Recent Activity" }) }),
1569
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "divide-y divide-gray-100", children: [
1570
+ state.actionHistory.slice(0, 5).map((action) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 flex items-center justify-between", children: [
1571
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1572
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "capitalize font-medium", children: action.type }),
1573
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-gray-500 ml-2 font-mono text-sm", children: action.licenseKey })
1574
+ ] }),
1575
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-500", children: formatDate(action.timestamp) })
1576
+ ] }, action.id)),
1577
+ state.actionHistory.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 text-gray-500 text-center", children: "No recent activity" })
1578
+ ] })
1579
+ ] })
1580
+ ] }),
1581
+ state.activeTab === "licenses" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4", children: [
1582
+ overview.licenses.map((license) => {
1583
+ var _a;
1584
+ return /* @__PURE__ */ jsxRuntime.jsx(
1585
+ LicenseCard,
1586
+ {
1587
+ license,
1588
+ selected: ((_a = state.selectedLicense) == null ? void 0 : _a.key) === license.key,
1589
+ onSelect: () => selectLicense(license),
1590
+ onActivate: () => {
1591
+ activateLicense(license.key, "machine-id-here");
1592
+ },
1593
+ onDeactivate: () => {
1594
+ deactivateLicense(license.key, "machine-id-here");
1595
+ }
1596
+ },
1597
+ license.key
1598
+ );
1599
+ }),
1600
+ overview.licenses.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "col-span-full text-center py-8 text-gray-500", children: "No licenses found. Contact support to purchase a license." })
1601
+ ] }),
1602
+ state.activeTab === "billing" && config.showBilling && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-6", children: [
1603
+ state.subscription && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white rounded-lg border border-gray-200 p-6", children: [
1604
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-semibold mb-4", children: "Current Subscription" }),
1605
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-4", children: [
1606
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1607
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm text-gray-500", children: "Plan" }),
1608
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-medium", children: state.subscription.plan })
1609
+ ] }),
1610
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1611
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm text-gray-500", children: "Status" }),
1612
+ /* @__PURE__ */ jsxRuntime.jsx(
1613
+ "div",
1614
+ {
1615
+ className: `font-medium capitalize ${state.subscription.status === "active" ? "text-green-600" : "text-orange-600"}`,
1616
+ children: state.subscription.status
1617
+ }
1618
+ )
1619
+ ] }),
1620
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1621
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm text-gray-500", children: "Current Period" }),
1622
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "font-medium", children: [
1623
+ formatDate(state.subscription.currentPeriodStart),
1624
+ " -",
1625
+ " ",
1626
+ formatDate(state.subscription.currentPeriodEnd)
1627
+ ] })
1628
+ ] }),
1629
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1630
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm text-gray-500", children: "Seats" }),
1631
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-medium", children: state.subscription.seats })
1632
+ ] })
1633
+ ] })
1634
+ ] }),
1635
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white rounded-lg border border-gray-200", children: [
1636
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 border-b border-gray-200", children: /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-semibold", children: "Invoices" }) }),
1637
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "divide-y divide-gray-100", children: [
1638
+ state.invoices.map((invoice) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 flex items-center justify-between", children: [
1639
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1640
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-medium", children: invoice.number }),
1641
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm text-gray-500", children: formatDate(invoice.issuedAt) })
1642
+ ] }),
1643
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-4", children: [
1644
+ /* @__PURE__ */ jsxRuntime.jsx(
1645
+ "span",
1646
+ {
1647
+ className: `px-2 py-1 rounded text-sm ${invoice.status === "paid" ? "bg-green-100 text-green-700" : "bg-gray-100 text-gray-700"}`,
1648
+ children: invoice.status
1649
+ }
1650
+ ),
1651
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-medium", children: [
1652
+ invoice.currency,
1653
+ " ",
1654
+ invoice.amount.toFixed(2)
1655
+ ] }),
1656
+ invoice.downloadUrl && /* @__PURE__ */ jsxRuntime.jsx(
1657
+ "a",
1658
+ {
1659
+ href: invoice.downloadUrl,
1660
+ className: "text-blue-500 hover:text-blue-600",
1661
+ target: "_blank",
1662
+ rel: "noopener noreferrer",
1663
+ children: "Download"
1664
+ }
1665
+ )
1666
+ ] })
1667
+ ] }, invoice.id)),
1668
+ state.invoices.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 text-gray-500 text-center", children: "No invoices" })
1669
+ ] })
1670
+ ] })
1671
+ ] }),
1672
+ state.activeTab === "seats" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white rounded-lg border border-gray-200 p-6", children: [
1673
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-semibold mb-4", children: "Seat Management" }),
1674
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-500 mb-4", children: "Select a license to manage seat assignments." })
1675
+ ] }),
1676
+ state.activeTab === "settings" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-white rounded-lg border border-gray-200 p-6", children: /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-semibold mb-4", children: "Portal Settings" }) })
1677
+ ] });
1678
+ };
1679
+ class ActivationService {
1680
+ constructor(config) {
1681
+ this.config = config;
1682
+ }
1683
+ async request(endpoint, options = {}) {
1684
+ const url = `${this.config.apiBaseUrl}${endpoint}`;
1685
+ const headers = {
1686
+ "Content-Type": "application/json",
1687
+ ...this.config.authToken && { Authorization: `Bearer ${this.config.authToken}` }
1688
+ };
1689
+ const response = await fetch(url, { ...options, headers });
1690
+ if (!response.ok) {
1691
+ const error = await response.json().catch(() => ({}));
1692
+ throw {
1693
+ code: `HTTP_${response.status}`,
1694
+ message: error.message || "Request failed",
1695
+ retryable: response.status >= 500,
1696
+ supportRequired: response.status === 403
1697
+ };
1698
+ }
1699
+ return response.json();
1700
+ }
1701
+ async validateKey(licenseKey) {
1702
+ return this.request("/licenses/validate", {
1703
+ method: "POST",
1704
+ body: JSON.stringify({ licenseKey })
1705
+ });
1706
+ }
1707
+ async checkHardware(licenseKey, machineId, fingerprint) {
1708
+ return this.request("/licenses/hardware-check", {
1709
+ method: "POST",
1710
+ body: JSON.stringify({ licenseKey, machineId, fingerprint })
1711
+ });
1712
+ }
1713
+ async activate(licenseKey, machineId, fingerprint, machineName) {
1714
+ return this.request(`/licenses/${encodeURIComponent(licenseKey)}/activate`, {
1715
+ method: "POST",
1716
+ body: JSON.stringify({ machineId, fingerprint, machineName })
1717
+ });
1718
+ }
1719
+ async getOfflineChallenge(licenseKey, machineId) {
1720
+ const result = await this.request("/licenses/offline/challenge", {
1721
+ method: "POST",
1722
+ body: JSON.stringify({ licenseKey, machineId })
1723
+ });
1724
+ return result.challenge;
1725
+ }
1726
+ async activateOffline(licenseKey, machineId, response) {
1727
+ return this.request("/licenses/offline/activate", {
1728
+ method: "POST",
1729
+ body: JSON.stringify({ licenseKey, machineId, response })
1730
+ });
1731
+ }
1732
+ }
1733
+ function formatLicenseKey(key) {
1734
+ const clean = key.replace(/[^A-Za-z0-9]/g, "").toUpperCase();
1735
+ const parts = [];
1736
+ for (let i = 0; i < clean.length; i += 4) {
1737
+ parts.push(clean.slice(i, i + 4));
1738
+ }
1739
+ return parts.slice(0, 5).join("-");
1740
+ }
1741
+ function validateKeyFormat(key) {
1742
+ const pattern = /^[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$/;
1743
+ return pattern.test(key);
1744
+ }
1745
+ async function getDefaultMachineName() {
1746
+ if (typeof window !== "undefined" && "userAgent" in navigator) {
1747
+ const ua = navigator.userAgent;
1748
+ if (ua.includes("Windows")) {
1749
+ return "Windows PC";
1750
+ }
1751
+ if (ua.includes("Mac")) {
1752
+ return "Mac";
1753
+ }
1754
+ if (ua.includes("Linux")) {
1755
+ return "Linux Machine";
1756
+ }
1757
+ if (ua.includes("Android")) {
1758
+ return "Android Device";
1759
+ }
1760
+ if (ua.includes("iOS")) {
1761
+ return "iOS Device";
1762
+ }
1763
+ }
1764
+ return "Unknown Device";
1765
+ }
1766
+ function useActivationWizard(config) {
1767
+ const [state, setState] = React.useState({
1768
+ step: "enter-key",
1769
+ licenseKey: "",
1770
+ validation: null,
1771
+ machineId: null,
1772
+ fingerprint: null,
1773
+ machineName: config.machineName || "",
1774
+ termsAccepted: false,
1775
+ error: null,
1776
+ activatedLicense: null,
1777
+ offlineChallenge: null
1778
+ });
1779
+ const service = React.useMemo(() => new ActivationService(config), [config]);
1780
+ const setLicenseKey = React.useCallback((key) => {
1781
+ const formatted = formatLicenseKey(key);
1782
+ setState((s) => ({ ...s, licenseKey: formatted, error: null }));
1783
+ }, []);
1784
+ const setMachineName = React.useCallback((name) => {
1785
+ setState((s) => ({ ...s, machineName: name }));
1786
+ }, []);
1787
+ const setTermsAccepted = React.useCallback((accepted) => {
1788
+ setState((s) => ({ ...s, termsAccepted: accepted }));
1789
+ }, []);
1790
+ const validateKey = React.useCallback(async () => {
1791
+ if (!validateKeyFormat(state.licenseKey)) {
1792
+ setState((s) => ({
1793
+ ...s,
1794
+ error: {
1795
+ code: "INVALID_FORMAT",
1796
+ message: "License key format is invalid. Expected format: XXXX-XXXX-XXXX-XXXX-XXXX",
1797
+ retryable: true
1798
+ }
1799
+ }));
1800
+ return;
1801
+ }
1802
+ setState((s) => ({ ...s, step: "validate", error: null }));
1803
+ try {
1804
+ const validation = await service.validateKey(state.licenseKey);
1805
+ if (!validation.valid) {
1806
+ setState((s) => ({
1807
+ ...s,
1808
+ step: "error",
1809
+ error: {
1810
+ code: validation.errorCode || "VALIDATION_FAILED",
1811
+ message: validation.errorMessage || "License key validation failed",
1812
+ retryable: true
1813
+ }
1814
+ }));
1815
+ return;
1816
+ }
1817
+ const fingerprintData = await FeatureGate.generateFingerprint();
1818
+ const machineId = await FeatureGate.generateMachineId();
1819
+ const machineName = state.machineName || await getDefaultMachineName();
1820
+ setState((s) => ({
1821
+ ...s,
1822
+ validation,
1823
+ machineId,
1824
+ fingerprint: fingerprintData,
1825
+ machineName,
1826
+ step: "hardware-check"
1827
+ }));
1828
+ const hwCheck = await service.checkHardware(state.licenseKey, machineId, fingerprintData);
1829
+ if (!hwCheck.allowed) {
1830
+ setState((s) => ({
1831
+ ...s,
1832
+ step: "error",
1833
+ error: {
1834
+ code: "HARDWARE_LIMIT",
1835
+ message: hwCheck.reason || "Maximum hardware activations reached",
1836
+ retryable: false,
1837
+ supportRequired: true
1838
+ }
1839
+ }));
1840
+ return;
1841
+ }
1842
+ setState((s) => ({ ...s, step: "terms" }));
1843
+ } catch (error) {
1844
+ setState((s) => ({
1845
+ ...s,
1846
+ step: "error",
1847
+ error
1848
+ }));
1849
+ }
1850
+ }, [state.licenseKey, state.machineName, service]);
1851
+ const acceptTermsAndActivate = React.useCallback(async () => {
1852
+ var _a, _b;
1853
+ if (!state.termsAccepted) {
1854
+ setState((s) => ({
1855
+ ...s,
1856
+ error: {
1857
+ code: "TERMS_NOT_ACCEPTED",
1858
+ message: "Please accept the terms of service to continue",
1859
+ retryable: true
1860
+ }
1861
+ }));
1862
+ return;
1863
+ }
1864
+ setState((s) => ({ ...s, step: "activating", error: null }));
1865
+ try {
1866
+ const license = await service.activate(
1867
+ state.licenseKey,
1868
+ state.machineId,
1869
+ state.fingerprint,
1870
+ state.machineName
1871
+ );
1872
+ setState((s) => ({
1873
+ ...s,
1874
+ step: "success",
1875
+ activatedLicense: license
1876
+ }));
1877
+ (_a = config.onActivationSuccess) == null ? void 0 : _a.call(config, license);
1878
+ } catch (error) {
1879
+ const activationError = error;
1880
+ setState((s) => ({
1881
+ ...s,
1882
+ step: "error",
1883
+ error: activationError
1884
+ }));
1885
+ (_b = config.onActivationError) == null ? void 0 : _b.call(config, activationError);
1886
+ }
1887
+ }, [state, service, config]);
1888
+ const startOfflineActivation = React.useCallback(async () => {
1889
+ if (!config.allowOfflineActivation) {
1890
+ return;
1891
+ }
1892
+ try {
1893
+ const machineId = state.machineId || await FeatureGate.generateMachineId();
1894
+ const challenge = await service.getOfflineChallenge(state.licenseKey, machineId);
1895
+ setState((s) => ({
1896
+ ...s,
1897
+ machineId,
1898
+ offlineChallenge: challenge
1899
+ }));
1900
+ } catch (error) {
1901
+ setState((s) => ({
1902
+ ...s,
1903
+ error
1904
+ }));
1905
+ }
1906
+ }, [state.licenseKey, state.machineId, service, config.allowOfflineActivation]);
1907
+ const submitOfflineResponse = React.useCallback(
1908
+ async (response) => {
1909
+ var _a, _b;
1910
+ setState((s) => ({ ...s, step: "activating", error: null }));
1911
+ try {
1912
+ const license = await service.activateOffline(state.licenseKey, state.machineId, response);
1913
+ setState((s) => ({
1914
+ ...s,
1915
+ step: "success",
1916
+ activatedLicense: license
1917
+ }));
1918
+ (_a = config.onActivationSuccess) == null ? void 0 : _a.call(config, license);
1919
+ } catch (error) {
1920
+ const activationError = error;
1921
+ setState((s) => ({
1922
+ ...s,
1923
+ step: "error",
1924
+ error: activationError
1925
+ }));
1926
+ (_b = config.onActivationError) == null ? void 0 : _b.call(config, activationError);
1927
+ }
1928
+ },
1929
+ [state.licenseKey, state.machineId, service, config]
1930
+ );
1931
+ const reset = React.useCallback(() => {
1932
+ setState({
1933
+ step: "enter-key",
1934
+ licenseKey: "",
1935
+ validation: null,
1936
+ machineId: null,
1937
+ fingerprint: null,
1938
+ machineName: config.machineName || "",
1939
+ termsAccepted: false,
1940
+ error: null,
1941
+ activatedLicense: null,
1942
+ offlineChallenge: null
1943
+ });
1944
+ }, [config.machineName]);
1945
+ const goBack = React.useCallback(() => {
1946
+ setState((s) => {
1947
+ const stepOrder = [
1948
+ "enter-key",
1949
+ "validate",
1950
+ "hardware-check",
1951
+ "terms",
1952
+ "activating",
1953
+ "success"
1954
+ ];
1955
+ const currentIndex = stepOrder.indexOf(s.step);
1956
+ if (currentIndex > 0) {
1957
+ return { ...s, step: stepOrder[currentIndex - 1], error: null };
1958
+ }
1959
+ return s;
1960
+ });
1961
+ }, []);
1962
+ return {
1963
+ state,
1964
+ setLicenseKey,
1965
+ setMachineName,
1966
+ setTermsAccepted,
1967
+ validateKey,
1968
+ acceptTermsAndActivate,
1969
+ startOfflineActivation,
1970
+ submitOfflineResponse,
1971
+ reset,
1972
+ goBack
1973
+ };
1974
+ }
1975
+ const StepIndicator = ({ currentStep }) => {
1976
+ const steps = [
1977
+ { id: "enter-key", label: "Enter Key" },
1978
+ { id: "validate", label: "Validate" },
1979
+ { id: "hardware-check", label: "Hardware" },
1980
+ { id: "terms", label: "Terms" },
1981
+ { id: "activating", label: "Activate" },
1982
+ { id: "success", label: "Done" }
1983
+ ];
1984
+ const stepOrder = [
1985
+ "enter-key",
1986
+ "validate",
1987
+ "hardware-check",
1988
+ "terms",
1989
+ "activating",
1990
+ "success"
1991
+ ];
1992
+ const currentIndex = stepOrder.indexOf(currentStep);
1993
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex items-center justify-between mb-8", children: steps.map((step, index) => /* @__PURE__ */ jsxRuntime.jsxs(React.Fragment, { children: [
1994
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center", children: [
1995
+ /* @__PURE__ */ jsxRuntime.jsx(
1996
+ "div",
1997
+ {
1998
+ className: `w-8 h-8 rounded-full flex items-center justify-center text-sm font-medium ${index < currentIndex ? "bg-green-500 text-white" : index === currentIndex ? "bg-blue-500 text-white" : "bg-gray-200 text-gray-500"}`,
1999
+ children: index < currentIndex ? "✓" : index + 1
2000
+ }
2001
+ ),
2002
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs mt-1 text-gray-500", children: step.label })
2003
+ ] }),
2004
+ index < steps.length - 1 && /* @__PURE__ */ jsxRuntime.jsx(
2005
+ "div",
2006
+ {
2007
+ className: `flex-1 h-0.5 mx-2 ${index < currentIndex ? "bg-green-500" : "bg-gray-200"}`
2008
+ }
2009
+ )
2010
+ ] }, step.id)) });
2011
+ };
2012
+ const ActivationWizard = (props) => {
2013
+ var _a;
2014
+ const { className, style, ...config } = props;
2015
+ const {
2016
+ state,
2017
+ setLicenseKey,
2018
+ setMachineName,
2019
+ setTermsAccepted,
2020
+ validateKey,
2021
+ acceptTermsAndActivate,
2022
+ startOfflineActivation,
2023
+ reset,
2024
+ goBack
2025
+ } = useActivationWizard(config);
2026
+ const [offlineResponse, setOfflineResponse] = React.useState("");
2027
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2028
+ "div",
2029
+ {
2030
+ className: `nice-activation-wizard bg-white rounded-lg shadow-lg p-8 max-w-xl mx-auto ${className || ""}`,
2031
+ style,
2032
+ children: [
2033
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center mb-6", children: [
2034
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl font-bold text-gray-900", children: "Activate License" }),
2035
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600 mt-1", children: "Follow the steps to activate your Nice2Dev license" })
2036
+ ] }),
2037
+ state.step !== "error" && /* @__PURE__ */ jsxRuntime.jsx(StepIndicator, { currentStep: state.step }),
2038
+ state.step === "error" && state.error && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
2039
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-red-50 border border-red-200 rounded-lg p-6 text-center", children: [
2040
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-red-500 text-4xl mb-3", children: "⚠️" }),
2041
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-semibold text-red-800", children: "Activation Failed" }),
2042
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-600 mt-2", children: state.error.message }),
2043
+ state.error.supportRequired && config.supportEmail && /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-sm text-gray-600 mt-4", children: [
2044
+ "Please contact support at",
2045
+ " ",
2046
+ /* @__PURE__ */ jsxRuntime.jsx("a", { href: `mailto:${config.supportEmail}`, className: "text-blue-500 hover:underline", children: config.supportEmail })
2047
+ ] })
2048
+ ] }),
2049
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3 justify-center", children: [
2050
+ state.error.retryable && /* @__PURE__ */ jsxRuntime.jsx(
2051
+ "button",
2052
+ {
2053
+ onClick: () => goBack(),
2054
+ className: "px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600",
2055
+ children: "Try Again"
2056
+ }
2057
+ ),
2058
+ /* @__PURE__ */ jsxRuntime.jsx(
2059
+ "button",
2060
+ {
2061
+ onClick: reset,
2062
+ className: "px-6 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300",
2063
+ children: "Start Over"
2064
+ }
2065
+ )
2066
+ ] })
2067
+ ] }),
2068
+ state.step === "enter-key" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-6", children: [
2069
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2070
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "License Key" }),
2071
+ /* @__PURE__ */ jsxRuntime.jsx(
2072
+ "input",
2073
+ {
2074
+ type: "text",
2075
+ value: state.licenseKey,
2076
+ onChange: (e) => setLicenseKey(e.target.value),
2077
+ placeholder: "XXXX-XXXX-XXXX-XXXX-XXXX",
2078
+ className: "w-full px-4 py-3 border border-gray-300 rounded-lg font-mono text-center text-lg tracking-wider focus:ring-2 focus:ring-blue-500 focus:border-blue-500",
2079
+ maxLength: 29
2080
+ }
2081
+ ),
2082
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-gray-500 mt-2", children: "Enter the license key you received upon purchase" })
2083
+ ] }),
2084
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2085
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Machine Name (optional)" }),
2086
+ /* @__PURE__ */ jsxRuntime.jsx(
2087
+ "input",
2088
+ {
2089
+ type: "text",
2090
+ value: state.machineName,
2091
+ onChange: (e) => setMachineName(e.target.value),
2092
+ placeholder: "My Work PC",
2093
+ className: "w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-blue-500"
2094
+ }
2095
+ )
2096
+ ] }),
2097
+ state.error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-red-600 text-sm bg-red-50 p-3 rounded-lg", children: state.error.message }),
2098
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3", children: [
2099
+ /* @__PURE__ */ jsxRuntime.jsx(
2100
+ "button",
2101
+ {
2102
+ onClick: validateKey,
2103
+ disabled: !state.licenseKey,
2104
+ className: "flex-1 px-6 py-3 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:bg-gray-300 disabled:cursor-not-allowed",
2105
+ children: "Continue"
2106
+ }
2107
+ ),
2108
+ config.onCancel && /* @__PURE__ */ jsxRuntime.jsx(
2109
+ "button",
2110
+ {
2111
+ onClick: config.onCancel,
2112
+ className: "px-6 py-3 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300",
2113
+ children: "Cancel"
2114
+ }
2115
+ )
2116
+ ] }),
2117
+ config.allowOfflineActivation && /* @__PURE__ */ jsxRuntime.jsx(
2118
+ "button",
2119
+ {
2120
+ onClick: startOfflineActivation,
2121
+ className: "w-full text-sm text-blue-500 hover:underline",
2122
+ children: "Activate offline →"
2123
+ }
2124
+ )
2125
+ ] }),
2126
+ (state.step === "validate" || state.step === "hardware-check") && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center py-8", children: [
2127
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 mx-auto" }),
2128
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-semibold mt-4", children: state.step === "validate" ? "Validating License..." : "Checking Hardware..." }),
2129
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-500 mt-2", children: state.step === "validate" ? "Verifying your license key with the server" : "Collecting hardware information for activation" })
2130
+ ] }),
2131
+ state.step === "terms" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-6", children: [
2132
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-gray-50 rounded-lg p-4", children: [
2133
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-semibold mb-3", children: "License Details" }),
2134
+ ((_a = state.validation) == null ? void 0 : _a.license) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-2 text-sm", children: [
2135
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-gray-500", children: "Tier:" }),
2136
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-medium capitalize", children: state.validation.license.tier }),
2137
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-gray-500", children: "Licensee:" }),
2138
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-medium", children: state.validation.license.licensee }),
2139
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-gray-500", children: "Max Machines:" }),
2140
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-medium", children: state.validation.license.maxMachines }),
2141
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-gray-500", children: "This Machine:" }),
2142
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-medium", children: state.machineName })
2143
+ ] })
2144
+ ] }),
2145
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border border-gray-200 rounded-lg p-4 max-h-48 overflow-y-auto text-sm text-gray-600", children: [
2146
+ /* @__PURE__ */ jsxRuntime.jsx("h4", { className: "font-semibold text-gray-800 mb-2", children: "Terms of Service" }),
2147
+ /* @__PURE__ */ jsxRuntime.jsx("p", { children: "By activating this license, you agree to the Nice2Dev Software License Agreement. This license grants you the right to use the software on the specified number of machines according to your license tier. Unauthorized redistribution, reverse engineering, or violation of these terms may result in license revocation." })
2148
+ ] }),
2149
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-start gap-3 cursor-pointer", children: [
2150
+ /* @__PURE__ */ jsxRuntime.jsx(
2151
+ "input",
2152
+ {
2153
+ type: "checkbox",
2154
+ checked: state.termsAccepted,
2155
+ onChange: (e) => setTermsAccepted(e.target.checked),
2156
+ className: "mt-1 w-4 h-4 rounded border-gray-300 text-blue-500 focus:ring-blue-500"
2157
+ }
2158
+ ),
2159
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-sm text-gray-700", children: [
2160
+ "I accept the",
2161
+ " ",
2162
+ config.termsUrl ? /* @__PURE__ */ jsxRuntime.jsx(
2163
+ "a",
2164
+ {
2165
+ href: config.termsUrl,
2166
+ className: "text-blue-500 hover:underline",
2167
+ target: "_blank",
2168
+ rel: "noopener noreferrer",
2169
+ children: "Terms of Service"
2170
+ }
2171
+ ) : "Terms of Service",
2172
+ config.privacyUrl && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2173
+ " ",
2174
+ "and",
2175
+ " ",
2176
+ /* @__PURE__ */ jsxRuntime.jsx(
2177
+ "a",
2178
+ {
2179
+ href: config.privacyUrl,
2180
+ className: "text-blue-500 hover:underline",
2181
+ target: "_blank",
2182
+ rel: "noopener noreferrer",
2183
+ children: "Privacy Policy"
2184
+ }
2185
+ )
2186
+ ] })
2187
+ ] })
2188
+ ] }),
2189
+ state.error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-red-600 text-sm bg-red-50 p-3 rounded-lg", children: state.error.message }),
2190
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3", children: [
2191
+ /* @__PURE__ */ jsxRuntime.jsx(
2192
+ "button",
2193
+ {
2194
+ onClick: goBack,
2195
+ className: "px-6 py-3 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300",
2196
+ children: "Back"
2197
+ }
2198
+ ),
2199
+ /* @__PURE__ */ jsxRuntime.jsx(
2200
+ "button",
2201
+ {
2202
+ onClick: acceptTermsAndActivate,
2203
+ disabled: !state.termsAccepted,
2204
+ className: "flex-1 px-6 py-3 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:bg-gray-300 disabled:cursor-not-allowed",
2205
+ children: "Activate License"
2206
+ }
2207
+ )
2208
+ ] })
2209
+ ] }),
2210
+ state.step === "activating" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center py-8", children: [
2211
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-spin rounded-full h-12 w-12 border-b-2 border-blue-500 mx-auto" }),
2212
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-semibold mt-4", children: "Activating License..." }),
2213
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-500 mt-2", children: "Please wait while we activate your license" })
2214
+ ] }),
2215
+ state.step === "success" && state.activatedLicense && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center space-y-6", children: [
2216
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-green-500 text-6xl", children: "✓" }),
2217
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2218
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-xl font-bold text-gray-900", children: "License Activated!" }),
2219
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600 mt-2", children: "Your license has been successfully activated on this machine." })
2220
+ ] }),
2221
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-green-50 rounded-lg p-4 text-left", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-2 text-sm", children: [
2222
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-gray-500", children: "License Key:" }),
2223
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-mono", children: state.activatedLicense.key }),
2224
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-gray-500", children: "Tier:" }),
2225
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "capitalize", children: state.activatedLicense.tier }),
2226
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-gray-500", children: "Machine:" }),
2227
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: state.machineName }),
2228
+ state.activatedLicense.expiresAt && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
2229
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-gray-500", children: "Expires:" }),
2230
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: new Date(state.activatedLicense.expiresAt).toLocaleDateString() })
2231
+ ] })
2232
+ ] }) }),
2233
+ /* @__PURE__ */ jsxRuntime.jsx(
2234
+ "button",
2235
+ {
2236
+ onClick: () => {
2237
+ var _a2;
2238
+ reset();
2239
+ (_a2 = config.onCancel) == null ? void 0 : _a2.call(config);
2240
+ },
2241
+ className: "px-8 py-3 bg-blue-500 text-white rounded-lg hover:bg-blue-600",
2242
+ children: "Done"
2243
+ }
2244
+ )
2245
+ ] }),
2246
+ state.offlineChallenge && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white rounded-lg p-6 max-w-md w-full", children: [
2247
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "text-lg font-semibold mb-4", children: "Offline Activation" }),
2248
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
2249
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2250
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-sm font-medium text-gray-700 mb-1", children: "Challenge Code (send this to support):" }),
2251
+ /* @__PURE__ */ jsxRuntime.jsx(
2252
+ "textarea",
2253
+ {
2254
+ readOnly: true,
2255
+ value: state.offlineChallenge,
2256
+ className: "w-full px-3 py-2 border border-gray-300 rounded font-mono text-xs h-20"
2257
+ }
2258
+ )
2259
+ ] }),
2260
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2261
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-sm font-medium text-gray-700 mb-1", children: "Response Code (from support):" }),
2262
+ /* @__PURE__ */ jsxRuntime.jsx(
2263
+ "textarea",
2264
+ {
2265
+ value: offlineResponse,
2266
+ onChange: (e) => setOfflineResponse(e.target.value),
2267
+ placeholder: "Paste the response code here...",
2268
+ className: "w-full px-3 py-2 border border-gray-300 rounded font-mono text-xs h-20"
2269
+ }
2270
+ )
2271
+ ] }),
2272
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3", children: [
2273
+ /* @__PURE__ */ jsxRuntime.jsx(
2274
+ "button",
2275
+ {
2276
+ onClick: () => {
2277
+ },
2278
+ disabled: !offlineResponse,
2279
+ className: "flex-1 px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 disabled:bg-gray-300",
2280
+ children: "Activate"
2281
+ }
2282
+ ),
2283
+ /* @__PURE__ */ jsxRuntime.jsx(
2284
+ "button",
2285
+ {
2286
+ onClick: () => {
2287
+ setOfflineResponse("");
2288
+ },
2289
+ className: "px-4 py-2 bg-gray-200 text-gray-700 rounded hover:bg-gray-300",
2290
+ children: "Cancel"
2291
+ }
2292
+ )
2293
+ ] })
2294
+ ] })
2295
+ ] }) })
2296
+ ]
2297
+ }
2298
+ );
2299
+ };
2300
+ class LicenseTransferService {
2301
+ constructor(config) {
2302
+ this.config = config;
2303
+ }
2304
+ async request(endpoint, options = {}) {
2305
+ const url = `${this.config.apiBaseUrl}${endpoint}`;
2306
+ const headers = {
2307
+ "Content-Type": "application/json",
2308
+ ...this.config.authToken && { Authorization: `Bearer ${this.config.authToken}` }
2309
+ };
2310
+ const response = await fetch(url, { ...options, headers });
2311
+ if (!response.ok) {
2312
+ const error = await response.json().catch(() => ({}));
2313
+ throw {
2314
+ code: `HTTP_${response.status}`,
2315
+ message: error.message || "Request failed"
2316
+ };
2317
+ }
2318
+ return response.json();
2319
+ }
2320
+ /** Get transferable licenses */
2321
+ async getTransferableLicenses() {
2322
+ return this.request("/licenses/transferable");
2323
+ }
2324
+ /** Validate recipient email/organization */
2325
+ async validateRecipient(email) {
2326
+ return this.request("/transfers/validate-recipient", {
2327
+ method: "POST",
2328
+ body: JSON.stringify({ email })
2329
+ });
2330
+ }
2331
+ /** Initiate transfer */
2332
+ async initiateTransfer(data) {
2333
+ return this.request("/transfers", {
2334
+ method: "POST",
2335
+ body: JSON.stringify(data)
2336
+ });
2337
+ }
2338
+ /** Get transfer by ID */
2339
+ async getTransfer(transferId) {
2340
+ return this.request(`/transfers/${transferId}`);
2341
+ }
2342
+ /** Get pending transfers */
2343
+ async getPendingTransfers() {
2344
+ return this.request("/transfers?status=pending,pending_approval");
2345
+ }
2346
+ /** Get transfer history */
2347
+ async getTransferHistory(licenseKey) {
2348
+ const params = licenseKey ? `?licenseKey=${encodeURIComponent(licenseKey)}` : "";
2349
+ return this.request(`/transfers/history${params}`);
2350
+ }
2351
+ /** Cancel transfer */
2352
+ async cancelTransfer(transferId, reason) {
2353
+ return this.request(`/transfers/${transferId}/cancel`, {
2354
+ method: "POST",
2355
+ body: JSON.stringify({ reason })
2356
+ });
2357
+ }
2358
+ /** Accept transfer (as recipient) */
2359
+ async acceptTransfer(transferId, token) {
2360
+ return this.request(`/transfers/${transferId}/accept`, {
2361
+ method: "POST",
2362
+ body: JSON.stringify({ token })
2363
+ });
2364
+ }
2365
+ /** Approve transfer (admin) */
2366
+ async approveTransfer(transferId) {
2367
+ return this.request(`/transfers/${transferId}/approve`, {
2368
+ method: "POST"
2369
+ });
2370
+ }
2371
+ /** Reject transfer (admin) */
2372
+ async rejectTransfer(transferId, reason) {
2373
+ return this.request(`/transfers/${transferId}/reject`, {
2374
+ method: "POST",
2375
+ body: JSON.stringify({ reason })
2376
+ });
2377
+ }
2378
+ }
2379
+ function getTransferStatusColor(status) {
2380
+ const colors = {
2381
+ pending: "#f59e0b",
2382
+ pending_approval: "#8b5cf6",
2383
+ approved: "#3b82f6",
2384
+ rejected: "#ef4444",
2385
+ completed: "#22c55e",
2386
+ cancelled: "#6b7280",
2387
+ expired: "#9ca3af"
2388
+ };
2389
+ return colors[status];
2390
+ }
2391
+ function getTransferStatusLabel(status) {
2392
+ const labels = {
2393
+ pending: "Pending Acceptance",
2394
+ pending_approval: "Awaiting Approval",
2395
+ approved: "Approved",
2396
+ rejected: "Rejected",
2397
+ completed: "Completed",
2398
+ cancelled: "Cancelled",
2399
+ expired: "Expired"
2400
+ };
2401
+ return labels[status];
2402
+ }
2403
+ const TRANSFER_REASONS = [
2404
+ { value: "employee_change", label: "Employee leaving/joining" },
2405
+ { value: "department_transfer", label: "Department transfer" },
2406
+ { value: "organization_sale", label: "Organization sale/merger" },
2407
+ { value: "other", label: "Other" }
2408
+ ];
2409
+ function useLicenseTransfer(config) {
2410
+ const [step, setStep] = React.useState("select-license");
2411
+ const [licenses, setLicenses] = React.useState([]);
2412
+ const [formData, setFormData] = React.useState({
2413
+ licenseKey: "",
2414
+ recipientEmail: "",
2415
+ recipientName: "",
2416
+ notes: "",
2417
+ transferReason: "employee_change",
2418
+ confirmDeactivation: false
2419
+ });
2420
+ const [selectedLicense, setSelectedLicense] = React.useState(null);
2421
+ const [recipientValidation, setRecipientValidation] = React.useState(null);
2422
+ const [transfer, setTransfer] = React.useState(null);
2423
+ const [loading, setLoading] = React.useState(false);
2424
+ const [error, setError] = React.useState(null);
2425
+ const service = React.useMemo(() => new LicenseTransferService(config), [config]);
2426
+ const loadLicenses = React.useCallback(async () => {
2427
+ var _a;
2428
+ setLoading(true);
2429
+ setError(null);
2430
+ try {
2431
+ const data = await service.getTransferableLicenses();
2432
+ setLicenses(data);
2433
+ } catch (err) {
2434
+ setError(err);
2435
+ (_a = config.onError) == null ? void 0 : _a.call(config, err);
2436
+ } finally {
2437
+ setLoading(false);
2438
+ }
2439
+ }, [service, config]);
2440
+ const selectLicense = React.useCallback((license) => {
2441
+ setSelectedLicense(license);
2442
+ setFormData((f) => ({ ...f, licenseKey: license.key }));
2443
+ setStep("enter-recipient");
2444
+ }, []);
2445
+ const validateRecipient = React.useCallback(async () => {
2446
+ if (!formData.recipientEmail) {
2447
+ return;
2448
+ }
2449
+ setLoading(true);
2450
+ try {
2451
+ const result = await service.validateRecipient(formData.recipientEmail);
2452
+ setRecipientValidation({
2453
+ valid: result.valid,
2454
+ isExternal: result.isExternal,
2455
+ warnings: result.warnings
2456
+ });
2457
+ if (result.valid && result.recipientInfo) {
2458
+ setFormData((f) => {
2459
+ var _a;
2460
+ return {
2461
+ ...f,
2462
+ recipientName: ((_a = result.recipientInfo) == null ? void 0 : _a.name) || f.recipientName
2463
+ };
2464
+ });
2465
+ }
2466
+ } catch (err) {
2467
+ setError(err);
2468
+ } finally {
2469
+ setLoading(false);
2470
+ }
2471
+ }, [formData.recipientEmail, service]);
2472
+ const proceedToReview = React.useCallback(() => {
2473
+ if ((recipientValidation == null ? void 0 : recipientValidation.valid) && formData.recipientEmail) {
2474
+ setStep("review");
2475
+ }
2476
+ }, [recipientValidation, formData.recipientEmail]);
2477
+ const initiateTransfer = React.useCallback(async () => {
2478
+ var _a, _b;
2479
+ if (!formData.confirmDeactivation) {
2480
+ setError({ code: "CONFIRMATION_REQUIRED", message: "Please confirm license deactivation" });
2481
+ return;
2482
+ }
2483
+ setStep("processing");
2484
+ setLoading(true);
2485
+ setError(null);
2486
+ try {
2487
+ const result = await service.initiateTransfer({
2488
+ licenseKey: formData.licenseKey,
2489
+ recipientEmail: formData.recipientEmail,
2490
+ recipientName: formData.recipientName,
2491
+ notes: formData.notes,
2492
+ reason: formData.transferReason
2493
+ });
2494
+ setTransfer(result);
2495
+ setStep("complete");
2496
+ (_a = config.onTransferComplete) == null ? void 0 : _a.call(config, result);
2497
+ } catch (err) {
2498
+ setError(err);
2499
+ setStep("review");
2500
+ (_b = config.onError) == null ? void 0 : _b.call(config, err);
2501
+ } finally {
2502
+ setLoading(false);
2503
+ }
2504
+ }, [formData, service, config]);
2505
+ const reset = React.useCallback(() => {
2506
+ setStep("select-license");
2507
+ setFormData({
2508
+ licenseKey: "",
2509
+ recipientEmail: "",
2510
+ recipientName: "",
2511
+ notes: "",
2512
+ transferReason: "employee_change",
2513
+ confirmDeactivation: false
2514
+ });
2515
+ setSelectedLicense(null);
2516
+ setRecipientValidation(null);
2517
+ setTransfer(null);
2518
+ setError(null);
2519
+ }, []);
2520
+ return {
2521
+ step,
2522
+ setStep,
2523
+ licenses,
2524
+ formData,
2525
+ setFormData,
2526
+ selectedLicense,
2527
+ recipientValidation,
2528
+ transfer,
2529
+ loading,
2530
+ error,
2531
+ loadLicenses,
2532
+ selectLicense,
2533
+ validateRecipient,
2534
+ proceedToReview,
2535
+ initiateTransfer,
2536
+ reset
2537
+ };
2538
+ }
2539
+ const TransferStatusBadge = ({ status }) => /* @__PURE__ */ jsxRuntime.jsx(
2540
+ "span",
2541
+ {
2542
+ className: "inline-flex items-center px-2.5 py-1 rounded-full text-sm font-medium",
2543
+ style: {
2544
+ backgroundColor: `${getTransferStatusColor(status)}20`,
2545
+ color: getTransferStatusColor(status)
2546
+ },
2547
+ children: getTransferStatusLabel(status)
2548
+ }
2549
+ );
2550
+ const LicenseTransfer = (props) => {
2551
+ var _a, _b;
2552
+ const { className, style, ...config } = props;
2553
+ const {
2554
+ step,
2555
+ licenses,
2556
+ formData,
2557
+ setFormData,
2558
+ selectedLicense,
2559
+ recipientValidation,
2560
+ transfer,
2561
+ loading,
2562
+ error,
2563
+ loadLicenses,
2564
+ selectLicense,
2565
+ validateRecipient,
2566
+ proceedToReview,
2567
+ initiateTransfer,
2568
+ reset,
2569
+ setStep
2570
+ } = useLicenseTransfer(config);
2571
+ React.useEffect(() => {
2572
+ loadLicenses();
2573
+ }, [loadLicenses]);
2574
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2575
+ "div",
2576
+ {
2577
+ className: `nice-license-transfer bg-white rounded-lg shadow-lg p-8 max-w-2xl mx-auto ${className || ""}`,
2578
+ style,
2579
+ children: [
2580
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl font-bold text-gray-900 mb-2", children: "Transfer License" }),
2581
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600 mb-6", children: "Transfer a license to another user or organization" }),
2582
+ error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-6 p-4 bg-red-50 border border-red-200 rounded-lg", children: /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-red-700", children: error.message }) }),
2583
+ step === "select-license" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
2584
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-semibold", children: "Select a license to transfer" }),
2585
+ loading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center py-8", children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-spin h-8 w-8 border-b-2 border-blue-500 rounded-full mx-auto" }) }) : licenses.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center py-8 text-gray-500", children: "No transferable licenses found" }) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid gap-4", children: licenses.map((license) => /* @__PURE__ */ jsxRuntime.jsxs(
2586
+ "div",
2587
+ {
2588
+ className: "p-4 border border-gray-200 rounded-lg hover:border-blue-500 cursor-pointer transition-colors",
2589
+ onClick: () => selectLicense(license),
2590
+ children: [
2591
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between items-start", children: [
2592
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2593
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-mono text-sm text-gray-500", children: license.key }),
2594
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-medium mt-1", children: license.licensee })
2595
+ ] }),
2596
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "px-2 py-1 bg-blue-100 text-blue-700 rounded text-sm capitalize", children: license.tier })
2597
+ ] }),
2598
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-2 text-sm text-gray-500", children: [
2599
+ license.activeSeats,
2600
+ "/",
2601
+ license.maxSeats ?? "∞",
2602
+ " seats used"
2603
+ ] })
2604
+ ]
2605
+ },
2606
+ license.key
2607
+ )) })
2608
+ ] }),
2609
+ step === "enter-recipient" && selectedLicense && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-6", children: [
2610
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 bg-gray-50 rounded-lg", children: [
2611
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm text-gray-500", children: "Transferring license:" }),
2612
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-mono", children: selectedLicense.key })
2613
+ ] }),
2614
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2615
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Recipient Email" }),
2616
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-2", children: [
2617
+ /* @__PURE__ */ jsxRuntime.jsx(
2618
+ "input",
2619
+ {
2620
+ type: "email",
2621
+ value: formData.recipientEmail,
2622
+ onChange: (e) => setFormData((f) => ({ ...f, recipientEmail: e.target.value })),
2623
+ onBlur: validateRecipient,
2624
+ placeholder: "recipient@company.com",
2625
+ className: "flex-1 px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
2626
+ }
2627
+ ),
2628
+ /* @__PURE__ */ jsxRuntime.jsx(
2629
+ "button",
2630
+ {
2631
+ onClick: validateRecipient,
2632
+ disabled: !formData.recipientEmail || loading,
2633
+ className: "px-4 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300 disabled:opacity-50",
2634
+ children: "Validate"
2635
+ }
2636
+ )
2637
+ ] }),
2638
+ recipientValidation && /* @__PURE__ */ jsxRuntime.jsxs(
2639
+ "div",
2640
+ {
2641
+ className: `mt-2 text-sm ${recipientValidation.valid ? "text-green-600" : "text-red-600"}`,
2642
+ children: [
2643
+ recipientValidation.valid ? "✓ Valid recipient" : "✗ Invalid recipient",
2644
+ recipientValidation.isExternal && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "ml-2 text-orange-600", children: "(External organization)" })
2645
+ ]
2646
+ }
2647
+ ),
2648
+ (_a = recipientValidation == null ? void 0 : recipientValidation.warnings) == null ? void 0 : _a.map((warning, i) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-1 text-sm text-orange-600", children: [
2649
+ "⚠ ",
2650
+ warning
2651
+ ] }, i))
2652
+ ] }),
2653
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2654
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Recipient Name" }),
2655
+ /* @__PURE__ */ jsxRuntime.jsx(
2656
+ "input",
2657
+ {
2658
+ type: "text",
2659
+ value: formData.recipientName,
2660
+ onChange: (e) => setFormData((f) => ({ ...f, recipientName: e.target.value })),
2661
+ placeholder: "John Doe",
2662
+ className: "w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
2663
+ }
2664
+ )
2665
+ ] }),
2666
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2667
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Transfer Reason" }),
2668
+ /* @__PURE__ */ jsxRuntime.jsx(
2669
+ "select",
2670
+ {
2671
+ value: formData.transferReason,
2672
+ onChange: (e) => setFormData((f) => ({
2673
+ ...f,
2674
+ transferReason: e.target.value
2675
+ })),
2676
+ className: "w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500",
2677
+ children: TRANSFER_REASONS.map((reason) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: reason.value, children: reason.label }, reason.value))
2678
+ }
2679
+ )
2680
+ ] }),
2681
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2682
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "block text-sm font-medium text-gray-700 mb-2", children: "Notes (optional)" }),
2683
+ /* @__PURE__ */ jsxRuntime.jsx(
2684
+ "textarea",
2685
+ {
2686
+ value: formData.notes,
2687
+ onChange: (e) => setFormData((f) => ({ ...f, notes: e.target.value })),
2688
+ placeholder: "Any additional information...",
2689
+ rows: 3,
2690
+ className: "w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500"
2691
+ }
2692
+ )
2693
+ ] }),
2694
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3", children: [
2695
+ /* @__PURE__ */ jsxRuntime.jsx(
2696
+ "button",
2697
+ {
2698
+ onClick: () => setStep("select-license"),
2699
+ className: "px-6 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300",
2700
+ children: "Back"
2701
+ }
2702
+ ),
2703
+ /* @__PURE__ */ jsxRuntime.jsx(
2704
+ "button",
2705
+ {
2706
+ onClick: proceedToReview,
2707
+ disabled: !(recipientValidation == null ? void 0 : recipientValidation.valid) || !formData.recipientName,
2708
+ className: "flex-1 px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:bg-gray-300",
2709
+ children: "Review Transfer"
2710
+ }
2711
+ )
2712
+ ] })
2713
+ ] }),
2714
+ step === "review" && selectedLicense && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-6", children: [
2715
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-semibold", children: "Review Transfer Details" }),
2716
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-gray-50 rounded-lg p-4 space-y-3", children: [
2717
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-2 gap-2 text-sm", children: [
2718
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-gray-500", children: "License Key:" }),
2719
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-mono", children: selectedLicense.key }),
2720
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-gray-500", children: "Current Licensee:" }),
2721
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: selectedLicense.licensee }),
2722
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-gray-500", children: "Tier:" }),
2723
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "capitalize", children: selectedLicense.tier }),
2724
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-gray-500", children: "Transfer To:" }),
2725
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: formData.recipientName }),
2726
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-gray-500", children: "Recipient Email:" }),
2727
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: formData.recipientEmail }),
2728
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-gray-500", children: "Reason:" }),
2729
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: (_b = TRANSFER_REASONS.find((r) => r.value === formData.transferReason)) == null ? void 0 : _b.label })
2730
+ ] }),
2731
+ formData.notes && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "pt-3 border-t border-gray-200", children: [
2732
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm text-gray-500 mb-1", children: "Notes:" }),
2733
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm", children: formData.notes })
2734
+ ] })
2735
+ ] }),
2736
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "p-4 bg-orange-50 border border-orange-200 rounded-lg", children: [
2737
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-semibold text-orange-800 mb-2", children: "⚠ Important" }),
2738
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-orange-700", children: "Once the transfer is completed, this license will be deactivated from all your machines and the new owner will need to activate it." })
2739
+ ] }),
2740
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-start gap-3 cursor-pointer", children: [
2741
+ /* @__PURE__ */ jsxRuntime.jsx(
2742
+ "input",
2743
+ {
2744
+ type: "checkbox",
2745
+ checked: formData.confirmDeactivation,
2746
+ onChange: (e) => setFormData((f) => ({ ...f, confirmDeactivation: e.target.checked })),
2747
+ className: "mt-1 w-4 h-4 rounded border-gray-300 text-blue-500 focus:ring-blue-500"
2748
+ }
2749
+ ),
2750
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm text-gray-700", children: "I understand that this license will be deactivated and transferred to the recipient. This action cannot be undone without admin intervention." })
2751
+ ] }),
2752
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-3", children: [
2753
+ /* @__PURE__ */ jsxRuntime.jsx(
2754
+ "button",
2755
+ {
2756
+ onClick: () => setStep("enter-recipient"),
2757
+ className: "px-6 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300",
2758
+ children: "Back"
2759
+ }
2760
+ ),
2761
+ /* @__PURE__ */ jsxRuntime.jsx(
2762
+ "button",
2763
+ {
2764
+ onClick: initiateTransfer,
2765
+ disabled: !formData.confirmDeactivation || loading,
2766
+ className: "flex-1 px-6 py-2 bg-blue-500 text-white rounded-lg hover:bg-blue-600 disabled:bg-gray-300",
2767
+ children: loading ? "Processing..." : "Initiate Transfer"
2768
+ }
2769
+ )
2770
+ ] })
2771
+ ] }),
2772
+ step === "processing" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center py-12", children: [
2773
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "animate-spin h-12 w-12 border-b-2 border-blue-500 rounded-full mx-auto" }),
2774
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-lg font-semibold mt-4", children: "Processing Transfer..." }),
2775
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-500", children: "Please wait while we process your transfer request" })
2776
+ ] }),
2777
+ step === "complete" && transfer && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center space-y-6", children: [
2778
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-green-500 text-6xl", children: "✓" }),
2779
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
2780
+ /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-xl font-bold", children: "Transfer Initiated" }),
2781
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600 mt-2", children: transfer.approvalRequired ? "Your transfer request has been submitted for approval." : "An email has been sent to the recipient with instructions to accept the transfer." })
2782
+ ] }),
2783
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "bg-gray-50 rounded-lg p-4 text-left", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-sm grid grid-cols-2 gap-2", children: [
2784
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-gray-500", children: "Transfer ID:" }),
2785
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-mono", children: transfer.id }),
2786
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-gray-500", children: "Status:" }),
2787
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx(TransferStatusBadge, { status: transfer.status }) }),
2788
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-gray-500", children: "Expires:" }),
2789
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: new Date(transfer.expiresAt).toLocaleDateString() })
2790
+ ] }) }),
2791
+ /* @__PURE__ */ jsxRuntime.jsx(
2792
+ "button",
2793
+ {
2794
+ onClick: reset,
2795
+ className: "px-8 py-3 bg-blue-500 text-white rounded-lg hover:bg-blue-600",
2796
+ children: "Done"
2797
+ }
2798
+ )
2799
+ ] })
2800
+ ]
2801
+ }
2802
+ );
2803
+ };
2804
+ class BillingService {
2805
+ constructor(config) {
2806
+ this.config = config;
2807
+ }
2808
+ async request(endpoint, options = {}) {
2809
+ const url = `${this.config.apiBaseUrl}${endpoint}`;
2810
+ const headers = {
2811
+ "Content-Type": "application/json",
2812
+ ...this.config.authToken && { Authorization: `Bearer ${this.config.authToken}` }
2813
+ };
2814
+ const response = await fetch(url, { ...options, headers });
2815
+ if (!response.ok) {
2816
+ const error = await response.json().catch(() => ({}));
2817
+ throw new Error(error.message || `Request failed: ${response.status}`);
2818
+ }
2819
+ return response.json();
2820
+ }
2821
+ /* ─────────────────────────────────────────────────────────────
2822
+ PRICING & PLANS
2823
+ ───────────────────────────────────────────────────────────── */
2824
+ /** Get all pricing plans */
2825
+ async getPlans(currency) {
2826
+ const params = currency ? `?currency=${currency}` : "";
2827
+ return this.request(`/billing/plans${params}`);
2828
+ }
2829
+ /** Get plan by ID */
2830
+ async getPlan(planId) {
2831
+ return this.request(`/billing/plans/${planId}`);
2832
+ }
2833
+ /** Compare plans (for upgrade/downgrade) */
2834
+ async comparePlans(currentPlanId, targetPlanId, seats) {
2835
+ return this.request("/billing/plans/compare", {
2836
+ method: "POST",
2837
+ body: JSON.stringify({ currentPlanId, targetPlanId, seats })
2838
+ });
2839
+ }
2840
+ /* ─────────────────────────────────────────────────────────────
2841
+ SUBSCRIPTIONS
2842
+ ───────────────────────────────────────────────────────────── */
2843
+ /** Get current subscription */
2844
+ async getSubscription() {
2845
+ try {
2846
+ return await this.request("/billing/subscription");
2847
+ } catch {
2848
+ return null;
2849
+ }
2850
+ }
2851
+ /** Get subscription by ID */
2852
+ async getSubscriptionById(subscriptionId) {
2853
+ return this.request(`/billing/subscriptions/${subscriptionId}`);
2854
+ }
2855
+ /** Create checkout session for new subscription */
2856
+ async createCheckoutSession(options) {
2857
+ const provider = options.provider || this.detectPreferredProvider();
2858
+ return this.request("/billing/checkout", {
2859
+ method: "POST",
2860
+ body: JSON.stringify({ ...options, provider })
2861
+ });
2862
+ }
2863
+ /** Create billing portal session */
2864
+ async createPortalSession(returnUrl) {
2865
+ return this.request("/billing/portal", {
2866
+ method: "POST",
2867
+ body: JSON.stringify({ returnUrl })
2868
+ });
2869
+ }
2870
+ /** Update subscription (change plan, seats) */
2871
+ async updateSubscription(options) {
2872
+ return this.request("/billing/subscription", {
2873
+ method: "PATCH",
2874
+ body: JSON.stringify(options)
2875
+ });
2876
+ }
2877
+ /** Cancel subscription */
2878
+ async cancelSubscription(options) {
2879
+ return this.request("/billing/subscription/cancel", {
2880
+ method: "POST",
2881
+ body: JSON.stringify(options || { atPeriodEnd: true })
2882
+ });
2883
+ }
2884
+ /** Resume canceled subscription */
2885
+ async resumeSubscription() {
2886
+ return this.request("/billing/subscription/resume", {
2887
+ method: "POST"
2888
+ });
2889
+ }
2890
+ /** Pause subscription (Paddle only) */
2891
+ async pauseSubscription() {
2892
+ return this.request("/billing/subscription/pause", {
2893
+ method: "POST"
2894
+ });
2895
+ }
2896
+ /* ─────────────────────────────────────────────────────────────
2897
+ PAYMENT METHODS
2898
+ ───────────────────────────────────────────────────────────── */
2899
+ /** Get saved payment methods */
2900
+ async getPaymentMethods() {
2901
+ return this.request("/billing/payment-methods");
2902
+ }
2903
+ /** Add payment method (redirect to Stripe/Paddle) */
2904
+ async addPaymentMethod(returnUrl) {
2905
+ return this.request("/billing/payment-methods/add", {
2906
+ method: "POST",
2907
+ body: JSON.stringify({ returnUrl })
2908
+ });
2909
+ }
2910
+ /** Remove payment method */
2911
+ async removePaymentMethod(paymentMethodId) {
2912
+ await this.request(`/billing/payment-methods/${paymentMethodId}`, {
2913
+ method: "DELETE"
2914
+ });
2915
+ }
2916
+ /** Set default payment method */
2917
+ async setDefaultPaymentMethod(paymentMethodId) {
2918
+ await this.request(`/billing/payment-methods/${paymentMethodId}/default`, {
2919
+ method: "POST"
2920
+ });
2921
+ }
2922
+ /* ─────────────────────────────────────────────────────────────
2923
+ INVOICES
2924
+ ───────────────────────────────────────────────────────────── */
2925
+ /** Get invoices */
2926
+ async getInvoices(options) {
2927
+ const params = new URLSearchParams();
2928
+ if (options == null ? void 0 : options.limit) {
2929
+ params.set("limit", String(options.limit));
2930
+ }
2931
+ if (options == null ? void 0 : options.offset) {
2932
+ params.set("offset", String(options.offset));
2933
+ }
2934
+ if (options == null ? void 0 : options.status) {
2935
+ params.set("status", options.status);
2936
+ }
2937
+ return this.request(`/billing/invoices?${params}`);
2938
+ }
2939
+ /** Get upcoming invoice preview */
2940
+ async getUpcomingInvoice() {
2941
+ try {
2942
+ return await this.request("/billing/invoices/upcoming");
2943
+ } catch {
2944
+ return null;
2945
+ }
2946
+ }
2947
+ /** Download invoice PDF */
2948
+ async downloadInvoice(invoiceId) {
2949
+ const response = await fetch(`${this.config.apiBaseUrl}/billing/invoices/${invoiceId}/pdf`, {
2950
+ headers: {
2951
+ Authorization: this.config.authToken ? `Bearer ${this.config.authToken}` : ""
2952
+ }
2953
+ });
2954
+ return response.blob();
2955
+ }
2956
+ /* ─────────────────────────────────────────────────────────────
2957
+ COUPONS & PROMOTIONS
2958
+ ───────────────────────────────────────────────────────────── */
2959
+ /** Validate coupon code */
2960
+ async validateCoupon(code, planId) {
2961
+ return this.request("/billing/coupons/validate", {
2962
+ method: "POST",
2963
+ body: JSON.stringify({ code, planId })
2964
+ });
2965
+ }
2966
+ /** Apply coupon to subscription */
2967
+ async applyCoupon(code) {
2968
+ return this.request("/billing/subscription/coupon", {
2969
+ method: "POST",
2970
+ body: JSON.stringify({ code })
2971
+ });
2972
+ }
2973
+ /* ─────────────────────────────────────────────────────────────
2974
+ USAGE-BASED BILLING
2975
+ ───────────────────────────────────────────────────────────── */
2976
+ /** Report usage */
2977
+ async reportUsage(records) {
2978
+ await this.request("/billing/usage", {
2979
+ method: "POST",
2980
+ body: JSON.stringify({ records })
2981
+ });
2982
+ }
2983
+ /** Get usage summary */
2984
+ async getUsageSummary(startDate, endDate) {
2985
+ return this.request(`/billing/usage/summary?start=${startDate}&end=${endDate}`);
2986
+ }
2987
+ /* ─────────────────────────────────────────────────────────────
2988
+ UTILITIES
2989
+ ───────────────────────────────────────────────────────────── */
2990
+ detectPreferredProvider() {
2991
+ if (this.config.stripePublishableKey) {
2992
+ return "stripe";
2993
+ }
2994
+ if (this.config.paddleVendorId) {
2995
+ return "paddle";
2996
+ }
2997
+ return "stripe";
2998
+ }
2999
+ /** Format price for display */
3000
+ formatPrice(amount, currency = "USD") {
3001
+ return new Intl.NumberFormat("en-US", {
3002
+ style: "currency",
3003
+ currency,
3004
+ minimumFractionDigits: 0,
3005
+ maximumFractionDigits: 2
3006
+ }).format(amount / 100);
3007
+ }
3008
+ /** Calculate savings for yearly billing */
3009
+ calculateYearlySavings(plan) {
3010
+ const monthlyTotal = plan.monthlyPrice * 12;
3011
+ const yearlyTotal = plan.yearlyPrice;
3012
+ const savings = monthlyTotal - yearlyTotal;
3013
+ const savingsPercent = Math.round(savings / monthlyTotal * 100);
3014
+ return { monthlyTotal, yearlyTotal, savings, savingsPercent };
3015
+ }
3016
+ }
3017
+ function createBillingService(config) {
3018
+ return new BillingService(config);
3019
+ }
3020
+ class UsageAnalyticsService {
3021
+ constructor(config) {
3022
+ this.config = config;
3023
+ }
3024
+ async request(endpoint, options = {}) {
3025
+ const url = `${this.config.apiBaseUrl}${endpoint}`;
3026
+ const headers = {
3027
+ "Content-Type": "application/json",
3028
+ ...this.config.authToken && { Authorization: `Bearer ${this.config.authToken}` }
3029
+ };
3030
+ const response = await fetch(url, { ...options, headers });
3031
+ if (!response.ok) {
3032
+ const error = await response.json().catch(() => ({}));
3033
+ throw new Error(error.message || `Request failed: ${response.status}`);
3034
+ }
3035
+ return response.json();
3036
+ }
3037
+ getTimeParams(range, granularity) {
3038
+ const params = new URLSearchParams({ range });
3039
+ if (granularity) {
3040
+ params.set("granularity", granularity);
3041
+ }
3042
+ if (this.config.organizationId) {
3043
+ params.set("org", this.config.organizationId);
3044
+ }
3045
+ return params.toString();
3046
+ }
3047
+ /** Get overview metrics */
3048
+ async getOverview(range = "30d") {
3049
+ return this.request(`/analytics/overview?${this.getTimeParams(range)}`);
3050
+ }
3051
+ /** Get license activity over time */
3052
+ async getLicenseActivity(range = "30d", granularity = "day") {
3053
+ return this.request(
3054
+ `/analytics/licenses/activity?${this.getTimeParams(range, granularity)}`
3055
+ );
3056
+ }
3057
+ /** Get user activity over time */
3058
+ async getUserActivity(range = "30d", granularity = "day") {
3059
+ return this.request(
3060
+ `/analytics/users/activity?${this.getTimeParams(range, granularity)}`
3061
+ );
3062
+ }
3063
+ /** Get feature usage data */
3064
+ async getFeatureUsage(range = "30d", features) {
3065
+ const params = this.getTimeParams(range);
3066
+ const featuresParam = features ? `&features=${features.join(",")}` : "";
3067
+ return this.request(`/analytics/features?${params}${featuresParam}`);
3068
+ }
3069
+ /** Get top users by activity */
3070
+ async getTopUsers(range = "30d", limit = 20) {
3071
+ return this.request(
3072
+ `/analytics/users/top?${this.getTimeParams(range)}&limit=${limit}`
3073
+ );
3074
+ }
3075
+ /** Get license utilization */
3076
+ async getLicenseUtilization() {
3077
+ const params = this.config.organizationId ? `?org=${this.config.organizationId}` : "";
3078
+ return this.request(`/analytics/licenses/utilization${params}`);
3079
+ }
3080
+ /** Get geographic distribution */
3081
+ async getGeographicData(range = "30d") {
3082
+ return this.request(`/analytics/geo?${this.getTimeParams(range)}`);
3083
+ }
3084
+ /** Get trends for key metrics */
3085
+ async getTrends(range = "30d") {
3086
+ return this.request(`/analytics/trends?${this.getTimeParams(range)}`);
3087
+ }
3088
+ /** Get retention cohorts */
3089
+ async getRetentionCohorts(months = 6) {
3090
+ const params = this.config.organizationId ? `?org=${this.config.organizationId}&months=${months}` : `?months=${months}`;
3091
+ return this.request(`/analytics/retention${params}`);
3092
+ }
3093
+ /** Export analytics data */
3094
+ async exportData(range, format) {
3095
+ const response = await fetch(
3096
+ `${this.config.apiBaseUrl}/analytics/export?${this.getTimeParams(range)}&format=${format}`,
3097
+ {
3098
+ headers: {
3099
+ Authorization: this.config.authToken ? `Bearer ${this.config.authToken}` : ""
3100
+ }
3101
+ }
3102
+ );
3103
+ return response.blob();
3104
+ }
3105
+ }
3106
+ function useUsageAnalytics(config) {
3107
+ const [state, setState] = React.useState({
3108
+ loading: true,
3109
+ error: null,
3110
+ timeRange: config.defaultTimeRange || "30d",
3111
+ overview: null,
3112
+ licenseActivity: [],
3113
+ userActivity: [],
3114
+ featureUsage: [],
3115
+ topUsers: [],
3116
+ licenseUtilization: [],
3117
+ geoData: [],
3118
+ trends: [],
3119
+ retentionCohorts: []
3120
+ });
3121
+ const service = React.useMemo(() => new UsageAnalyticsService(config), [config]);
3122
+ const loadData = React.useCallback(
3123
+ async (range = state.timeRange) => {
3124
+ setState((s) => ({ ...s, loading: true, error: null, timeRange: range }));
3125
+ try {
3126
+ const [
3127
+ overview,
3128
+ licenseActivity,
3129
+ userActivity,
3130
+ featureUsage,
3131
+ topUsers,
3132
+ licenseUtilization,
3133
+ geoData,
3134
+ trends,
3135
+ retentionCohorts
3136
+ ] = await Promise.all([
3137
+ service.getOverview(range),
3138
+ service.getLicenseActivity(range),
3139
+ service.getUserActivity(range),
3140
+ service.getFeatureUsage(range),
3141
+ service.getTopUsers(range),
3142
+ service.getLicenseUtilization(),
3143
+ service.getGeographicData(range),
3144
+ service.getTrends(range),
3145
+ service.getRetentionCohorts()
3146
+ ]);
3147
+ setState((s) => ({
3148
+ ...s,
3149
+ loading: false,
3150
+ overview,
3151
+ licenseActivity,
3152
+ userActivity,
3153
+ featureUsage,
3154
+ topUsers,
3155
+ licenseUtilization,
3156
+ geoData,
3157
+ trends,
3158
+ retentionCohorts
3159
+ }));
3160
+ } catch (error) {
3161
+ setState((s) => ({
3162
+ ...s,
3163
+ loading: false,
3164
+ error: error.message
3165
+ }));
3166
+ }
3167
+ },
3168
+ [service, state.timeRange]
3169
+ );
3170
+ React.useEffect(() => {
3171
+ loadData();
3172
+ }, []);
3173
+ React.useEffect(() => {
3174
+ if (config.refreshInterval && config.refreshInterval > 0) {
3175
+ const interval = setInterval(() => loadData(), config.refreshInterval);
3176
+ return () => clearInterval(interval);
3177
+ }
3178
+ }, [config.refreshInterval, loadData]);
3179
+ const setTimeRange = React.useCallback(
3180
+ (range) => {
3181
+ loadData(range);
3182
+ },
3183
+ [loadData]
3184
+ );
3185
+ const exportData = React.useCallback(
3186
+ async (format) => {
3187
+ const blob = await service.exportData(state.timeRange, format);
3188
+ const url = URL.createObjectURL(blob);
3189
+ const a = document.createElement("a");
3190
+ a.href = url;
3191
+ a.download = `analytics-${state.timeRange}.${format}`;
3192
+ a.click();
3193
+ URL.revokeObjectURL(url);
3194
+ },
3195
+ [service, state.timeRange]
3196
+ );
3197
+ return {
3198
+ state,
3199
+ setTimeRange,
3200
+ refresh: loadData,
3201
+ exportData
3202
+ };
3203
+ }
3204
+ const MetricCard = ({ title, value, change, suffix, loading }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white rounded-lg border border-gray-200 p-4", children: [
3205
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm text-gray-500 mb-1", children: title }),
3206
+ loading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-8 bg-gray-100 rounded animate-pulse" }) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
3207
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-2xl font-bold", children: [
3208
+ typeof value === "number" ? value.toLocaleString() : value,
3209
+ suffix && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-sm font-normal text-gray-500 ml-1", children: suffix })
3210
+ ] }),
3211
+ change !== void 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `text-sm mt-1 ${change >= 0 ? "text-green-600" : "text-red-600"}`, children: [
3212
+ change >= 0 ? "↑" : "↓",
3213
+ " ",
3214
+ Math.abs(change).toFixed(1),
3215
+ "%"
3216
+ ] })
3217
+ ] })
3218
+ ] });
3219
+ const TimeRangeSelector = ({ value, onChange }) => {
3220
+ const options = [
3221
+ { value: "24h", label: "24 Hours" },
3222
+ { value: "7d", label: "7 Days" },
3223
+ { value: "30d", label: "30 Days" },
3224
+ { value: "90d", label: "90 Days" },
3225
+ { value: "1y", label: "1 Year" },
3226
+ { value: "all", label: "All Time" }
3227
+ ];
3228
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "flex gap-1 bg-gray-100 rounded-lg p-1", children: options.map((opt) => /* @__PURE__ */ jsxRuntime.jsx(
3229
+ "button",
3230
+ {
3231
+ onClick: () => onChange(opt.value),
3232
+ className: `px-3 py-1.5 rounded-md text-sm font-medium transition-colors ${value === opt.value ? "bg-white text-gray-900 shadow-sm" : "text-gray-600 hover:text-gray-900"}`,
3233
+ children: opt.label
3234
+ },
3235
+ opt.value
3236
+ )) });
3237
+ };
3238
+ const FeatureUsageTable = ({ data, loading }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white rounded-lg border border-gray-200 overflow-hidden", children: [
3239
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 border-b border-gray-200", children: /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-semibold", children: "Feature Usage" }) }),
3240
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-full", children: [
3241
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { className: "bg-gray-50", children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
3242
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase", children: "Feature" }),
3243
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase", children: "Total Uses" }),
3244
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase", children: "Users" }),
3245
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase", children: "Avg/User" }),
3246
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase", children: "Trend" })
3247
+ ] }) }),
3248
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { className: "divide-y divide-gray-100", children: loading ? /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx("td", { colSpan: 5, className: "px-4 py-8 text-center text-gray-500", children: "Loading..." }) }) : data.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx("td", { colSpan: 5, className: "px-4 py-8 text-center text-gray-500", children: "No data available" }) }) : data.map((feature) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "hover:bg-gray-50", children: [
3249
+ /* @__PURE__ */ jsxRuntime.jsxs("td", { className: "px-4 py-3", children: [
3250
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-medium", children: feature.featureName }),
3251
+ feature.category && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-gray-500", children: feature.category })
3252
+ ] }),
3253
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-4 py-3 text-right font-mono", children: feature.totalUses.toLocaleString() }),
3254
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-4 py-3 text-right font-mono", children: feature.uniqueUsers.toLocaleString() }),
3255
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-4 py-3 text-right font-mono", children: feature.avgUsesPerUser.toFixed(1) }),
3256
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-4 py-3 text-right", children: /* @__PURE__ */ jsxRuntime.jsxs(
3257
+ "span",
3258
+ {
3259
+ className: `inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${feature.trend > 0 ? "bg-green-100 text-green-800" : feature.trend < 0 ? "bg-red-100 text-red-800" : "bg-gray-100 text-gray-800"}`,
3260
+ children: [
3261
+ feature.trend > 0 ? "+" : "",
3262
+ feature.trend.toFixed(1),
3263
+ "%"
3264
+ ]
3265
+ }
3266
+ ) })
3267
+ ] }, feature.featureId)) })
3268
+ ] }) })
3269
+ ] });
3270
+ const TopUsersTable = ({ data, loading }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white rounded-lg border border-gray-200 overflow-hidden", children: [
3271
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 border-b border-gray-200", children: /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-semibold", children: "Top Active Users" }) }),
3272
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "overflow-x-auto", children: /* @__PURE__ */ jsxRuntime.jsxs("table", { className: "w-full", children: [
3273
+ /* @__PURE__ */ jsxRuntime.jsx("thead", { className: "bg-gray-50", children: /* @__PURE__ */ jsxRuntime.jsxs("tr", { children: [
3274
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase", children: "User" }),
3275
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase", children: "Sessions" }),
3276
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase", children: "Duration" }),
3277
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-4 py-3 text-right text-xs font-medium text-gray-500 uppercase", children: "Features" }),
3278
+ /* @__PURE__ */ jsxRuntime.jsx("th", { className: "px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase", children: "Last Active" })
3279
+ ] }) }),
3280
+ /* @__PURE__ */ jsxRuntime.jsx("tbody", { className: "divide-y divide-gray-100", children: loading ? /* @__PURE__ */ jsxRuntime.jsx("tr", { children: /* @__PURE__ */ jsxRuntime.jsx("td", { colSpan: 5, className: "px-4 py-8 text-center text-gray-500", children: "Loading..." }) }) : data.map((user) => /* @__PURE__ */ jsxRuntime.jsxs("tr", { className: "hover:bg-gray-50", children: [
3281
+ /* @__PURE__ */ jsxRuntime.jsxs("td", { className: "px-4 py-3", children: [
3282
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-medium", children: user.displayName || user.email }),
3283
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-gray-500 capitalize", children: user.tier })
3284
+ ] }),
3285
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-4 py-3 text-right font-mono", children: user.sessionCount.toLocaleString() }),
3286
+ /* @__PURE__ */ jsxRuntime.jsxs("td", { className: "px-4 py-3 text-right font-mono", children: [
3287
+ Math.round(user.totalDuration / 60),
3288
+ "h"
3289
+ ] }),
3290
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-4 py-3 text-right font-mono", children: user.featuresUsed }),
3291
+ /* @__PURE__ */ jsxRuntime.jsx("td", { className: "px-4 py-3 text-sm text-gray-500", children: new Date(user.lastActive).toLocaleDateString() })
3292
+ ] }, user.userId)) })
3293
+ ] }) })
3294
+ ] });
3295
+ const LicenseUtilizationChart = ({ data, loading }) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white rounded-lg border border-gray-200", children: [
3296
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 border-b border-gray-200", children: /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-semibold", children: "License Utilization" }) }),
3297
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-4 space-y-4", children: loading ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center text-gray-500", children: "Loading..." }) : data.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-center text-gray-500", children: "No licenses" }) : data.map((license) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3298
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex justify-between text-sm mb-1", children: [
3299
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium truncate max-w-[200px]", children: license.licensee }),
3300
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-gray-500", children: [
3301
+ license.usedSeats,
3302
+ "/",
3303
+ license.totalSeats,
3304
+ " seats"
3305
+ ] })
3306
+ ] }),
3307
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "h-2 bg-gray-100 rounded-full overflow-hidden", children: /* @__PURE__ */ jsxRuntime.jsx(
3308
+ "div",
3309
+ {
3310
+ className: `h-full rounded-full ${license.utilizationPercent >= 90 ? "bg-red-500" : license.utilizationPercent >= 70 ? "bg-yellow-500" : "bg-green-500"}`,
3311
+ style: { width: `${license.utilizationPercent}%` }
3312
+ }
3313
+ ) })
3314
+ ] }, license.licenseKey)) })
3315
+ ] });
3316
+ const UsageAnalyticsDashboard = (props) => {
3317
+ var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l;
3318
+ const { className, style, ...config } = props;
3319
+ const { state, setTimeRange, refresh, exportData } = useUsageAnalytics(config);
3320
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: `nice-usage-analytics ${className || ""}`, style, children: [
3321
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between mb-6", children: [
3322
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
3323
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl font-bold text-gray-900", children: "Usage Analytics" }),
3324
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-gray-600", children: "Monitor license usage and feature adoption" })
3325
+ ] }),
3326
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-4", children: [
3327
+ /* @__PURE__ */ jsxRuntime.jsx(TimeRangeSelector, { value: state.timeRange, onChange: setTimeRange }),
3328
+ /* @__PURE__ */ jsxRuntime.jsx(
3329
+ "button",
3330
+ {
3331
+ onClick: () => refresh(),
3332
+ className: "p-2 text-gray-500 hover:text-gray-700 rounded-lg hover:bg-gray-100",
3333
+ title: "Refresh",
3334
+ children: "↻"
3335
+ }
3336
+ ),
3337
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "relative", children: /* @__PURE__ */ jsxRuntime.jsx(
3338
+ "button",
3339
+ {
3340
+ className: "px-4 py-2 bg-gray-200 text-gray-700 rounded-lg hover:bg-gray-300",
3341
+ onClick: () => exportData("csv"),
3342
+ children: "Export"
3343
+ }
3344
+ ) })
3345
+ ] })
3346
+ ] }),
3347
+ state.error && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-6 p-4 bg-red-50 border border-red-200 rounded-lg text-red-700", children: state.error }),
3348
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 md:grid-cols-2 lg:grid-cols-5 gap-4 mb-6", children: [
3349
+ /* @__PURE__ */ jsxRuntime.jsx(
3350
+ MetricCard,
3351
+ {
3352
+ title: "Active Licenses",
3353
+ value: ((_a = state.overview) == null ? void 0 : _a.activeLicenses) ?? 0,
3354
+ change: (_b = state.overview) == null ? void 0 : _b.activeLicensesChange,
3355
+ loading: state.loading
3356
+ }
3357
+ ),
3358
+ /* @__PURE__ */ jsxRuntime.jsx(
3359
+ MetricCard,
3360
+ {
3361
+ title: "Active Users",
3362
+ value: ((_c = state.overview) == null ? void 0 : _c.activeUsers) ?? 0,
3363
+ change: (_d = state.overview) == null ? void 0 : _d.activeUsersChange,
3364
+ loading: state.loading
3365
+ }
3366
+ ),
3367
+ /* @__PURE__ */ jsxRuntime.jsx(
3368
+ MetricCard,
3369
+ {
3370
+ title: "Feature Uses",
3371
+ value: ((_e = state.overview) == null ? void 0 : _e.featureUses) ?? 0,
3372
+ change: (_f = state.overview) == null ? void 0 : _f.featureUsesChange,
3373
+ loading: state.loading
3374
+ }
3375
+ ),
3376
+ /* @__PURE__ */ jsxRuntime.jsx(
3377
+ MetricCard,
3378
+ {
3379
+ title: "Avg Session",
3380
+ value: ((_h = (_g = state.overview) == null ? void 0 : _g.avgSessionDuration) == null ? void 0 : _h.toFixed(1)) ?? "0",
3381
+ suffix: "min",
3382
+ change: (_i = state.overview) == null ? void 0 : _i.avgSessionDurationChange,
3383
+ loading: state.loading
3384
+ }
3385
+ ),
3386
+ /* @__PURE__ */ jsxRuntime.jsx(
3387
+ MetricCard,
3388
+ {
3389
+ title: "Utilization",
3390
+ value: `${((_k = (_j = state.overview) == null ? void 0 : _j.utilizationRate) == null ? void 0 : _k.toFixed(0)) ?? 0}%`,
3391
+ change: (_l = state.overview) == null ? void 0 : _l.utilizationRateChange,
3392
+ loading: state.loading
3393
+ }
3394
+ )
3395
+ ] }),
3396
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 lg:grid-cols-3 gap-6 mb-6", children: [
3397
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "lg:col-span-2", children: /* @__PURE__ */ jsxRuntime.jsx(FeatureUsageTable, { data: state.featureUsage, loading: state.loading }) }),
3398
+ /* @__PURE__ */ jsxRuntime.jsx("div", { children: /* @__PURE__ */ jsxRuntime.jsx(LicenseUtilizationChart, { data: state.licenseUtilization, loading: state.loading }) })
3399
+ ] }),
3400
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-6", children: /* @__PURE__ */ jsxRuntime.jsx(TopUsersTable, { data: state.topUsers, loading: state.loading }) }),
3401
+ state.geoData.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "bg-white rounded-lg border border-gray-200 p-4", children: [
3402
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-semibold mb-4", children: "Geographic Distribution" }),
3403
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4", children: state.geoData.slice(0, 6).map((geo) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-center", children: [
3404
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-2xl mb-1", children: getCountryFlag(geo.countryCode) }),
3405
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-sm font-medium", children: geo.country }),
3406
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "text-xs text-gray-500", children: [
3407
+ geo.users,
3408
+ " users"
3409
+ ] })
3410
+ ] }, geo.countryCode)) })
3411
+ ] })
3412
+ ] });
3413
+ };
3414
+ function getCountryFlag(countryCode) {
3415
+ const codePoints = countryCode.toUpperCase().split("").map((char) => 127397 + char.charCodeAt(0));
3416
+ return String.fromCodePoint(...codePoints);
3417
+ }
3418
+ class RefundService {
3419
+ constructor(config) {
3420
+ this.config = config;
3421
+ }
3422
+ async request(endpoint, options = {}) {
3423
+ const url = `${this.config.apiBaseUrl}${endpoint}`;
3424
+ const headers = {
3425
+ "Content-Type": "application/json",
3426
+ ...this.config.authToken && { Authorization: `Bearer ${this.config.authToken}` }
3427
+ };
3428
+ const response = await fetch(url, { ...options, headers });
3429
+ if (!response.ok) {
3430
+ const error = await response.json().catch(() => ({}));
3431
+ throw new Error(error.message || `Request failed: ${response.status}`);
3432
+ }
3433
+ return response.json();
3434
+ }
3435
+ /* ─────────────────────────────────────────────────────────────
3436
+ REFUND ELIGIBILITY
3437
+ ───────────────────────────────────────────────────────────── */
3438
+ /** Check if license is eligible for refund */
3439
+ async checkEligibility(licenseKey) {
3440
+ return this.request(
3441
+ `/refunds/eligibility/${encodeURIComponent(licenseKey)}`
3442
+ );
3443
+ }
3444
+ /** Calculate refund amount */
3445
+ calculateRefundAmount(originalAmount, daysSincePurchase) {
3446
+ const policy = this.config.policy;
3447
+ if (daysSincePurchase > policy.refundPeriodDays) {
3448
+ return { amount: 0, percent: 0 };
3449
+ }
3450
+ if (daysSincePurchase <= policy.fullRefundDays) {
3451
+ return { amount: originalAmount, percent: 100 };
3452
+ }
3453
+ if (policy.partialRefundPercent) {
3454
+ const amount = Math.round(originalAmount * (policy.partialRefundPercent / 100));
3455
+ return { amount, percent: policy.partialRefundPercent };
3456
+ }
3457
+ if (policy.usageBasedRefundPercent !== void 0) {
3458
+ const usedDays = daysSincePurchase - policy.fullRefundDays;
3459
+ const remainingDays = policy.refundPeriodDays - policy.fullRefundDays;
3460
+ const usagePercent = Math.min(100, usedDays / remainingDays * 100);
3461
+ const refundPercent = Math.max(0, policy.usageBasedRefundPercent - usagePercent);
3462
+ const amount = Math.round(originalAmount * (refundPercent / 100));
3463
+ return { amount, percent: refundPercent };
3464
+ }
3465
+ return { amount: originalAmount, percent: 100 };
3466
+ }
3467
+ /* ─────────────────────────────────────────────────────────────
3468
+ REFUND REQUESTS
3469
+ ───────────────────────────────────────────────────────────── */
3470
+ /** Create refund request */
3471
+ async createRefundRequest(data) {
3472
+ return this.request("/refunds", {
3473
+ method: "POST",
3474
+ body: JSON.stringify(data)
3475
+ });
3476
+ }
3477
+ /** Get refund request by ID */
3478
+ async getRefundRequest(requestId) {
3479
+ return this.request(`/refunds/${requestId}`);
3480
+ }
3481
+ /** Get refund requests for customer */
3482
+ async getCustomerRefundRequests(customerId) {
3483
+ const endpoint = customerId ? `/refunds?customerId=${customerId}` : "/refunds";
3484
+ return this.request(endpoint);
3485
+ }
3486
+ /** Get pending refund requests (admin) */
3487
+ async getPendingRequests(limit = 50, offset = 0) {
3488
+ return this.request(`/refunds/pending?limit=${limit}&offset=${offset}`);
3489
+ }
3490
+ /** Cancel refund request (customer) */
3491
+ async cancelRefundRequest(requestId) {
3492
+ return this.request(`/refunds/${requestId}/cancel`, {
3493
+ method: "POST"
3494
+ });
3495
+ }
3496
+ /** Add attachment to refund request */
3497
+ async addAttachment(requestId, file) {
3498
+ const formData = new FormData();
3499
+ formData.append("file", file);
3500
+ const response = await fetch(`${this.config.apiBaseUrl}/refunds/${requestId}/attachments`, {
3501
+ method: "POST",
3502
+ headers: {
3503
+ Authorization: this.config.authToken ? `Bearer ${this.config.authToken}` : ""
3504
+ },
3505
+ body: formData
3506
+ });
3507
+ if (!response.ok) {
3508
+ throw new Error("Failed to upload attachment");
3509
+ }
3510
+ return response.json();
3511
+ }
3512
+ /* ─────────────────────────────────────────────────────────────
3513
+ ADMIN OPERATIONS
3514
+ ───────────────────────────────────────────────────────────── */
3515
+ /** Approve refund request (admin) */
3516
+ async approveRefund(requestId, options) {
3517
+ return this.request(`/refunds/${requestId}/approve`, {
3518
+ method: "POST",
3519
+ body: JSON.stringify(options || {})
3520
+ });
3521
+ }
3522
+ /** Reject refund request (admin) */
3523
+ async rejectRefund(requestId, reason) {
3524
+ return this.request(`/refunds/${requestId}/reject`, {
3525
+ method: "POST",
3526
+ body: JSON.stringify({ reason })
3527
+ });
3528
+ }
3529
+ /** Process refund (trigger payment refund) */
3530
+ async processRefund(requestId) {
3531
+ var _a, _b;
3532
+ const result = await this.request(`/refunds/${requestId}/process`, {
3533
+ method: "POST"
3534
+ });
3535
+ (_b = (_a = this.config).onRefundProcessed) == null ? void 0 : _b.call(_a, result);
3536
+ return result;
3537
+ }
3538
+ /* ─────────────────────────────────────────────────────────────
3539
+ LICENSE REVOCATION
3540
+ ───────────────────────────────────────────────────────────── */
3541
+ /** Revoke license */
3542
+ async revokeLicense(licenseKey, reason, options) {
3543
+ var _a, _b;
3544
+ const result = await this.request("/revocations", {
3545
+ method: "POST",
3546
+ body: JSON.stringify({
3547
+ licenseKey,
3548
+ reason,
3549
+ ...options
3550
+ })
3551
+ });
3552
+ (_b = (_a = this.config).onLicenseRevoked) == null ? void 0 : _b.call(_a, result);
3553
+ return result;
3554
+ }
3555
+ /** Get revocation history */
3556
+ async getRevocationHistory(licenseKey) {
3557
+ const endpoint = licenseKey ? `/revocations?licenseKey=${encodeURIComponent(licenseKey)}` : "/revocations";
3558
+ return this.request(endpoint);
3559
+ }
3560
+ /** Reactivate revoked license (if allowed) */
3561
+ async reactivateLicense(revocationId, reason) {
3562
+ return this.request(`/revocations/${revocationId}/reactivate`, {
3563
+ method: "POST",
3564
+ body: JSON.stringify({ reason })
3565
+ });
3566
+ }
3567
+ /* ─────────────────────────────────────────────────────────────
3568
+ STATISTICS
3569
+ ───────────────────────────────────────────────────────────── */
3570
+ /** Get refund statistics */
3571
+ async getRefundStats(startDate, endDate) {
3572
+ return this.request(`/refunds/stats?start=${startDate}&end=${endDate}`);
3573
+ }
3574
+ }
3575
+ const DEFAULT_REFUND_POLICY = {
3576
+ refundPeriodDays: 30,
3577
+ autoApprovalTiers: ["trial", "personal"],
3578
+ autoApprovalMaxAmount: 1e4,
3579
+ // $100
3580
+ maxRefundsPerYear: 3,
3581
+ requireReason: true,
3582
+ requireDetails: false,
3583
+ fullRefundDays: 14,
3584
+ partialRefundPercent: 50
3585
+ };
3586
+ function createRefundService(config) {
3587
+ return new RefundService(config);
3588
+ }
3589
+ class BuildLicenseValidator {
3590
+ constructor(config = {}) {
3591
+ this.licenseInfo = null;
3592
+ this.validationResult = null;
3593
+ this.config = {
3594
+ failOnInvalid: true,
3595
+ failOnExpired: true,
3596
+ allowOffline: true,
3597
+ validationTimeout: 1e4,
3598
+ ...config
3599
+ };
3600
+ }
3601
+ /** Get license key from config or environment */
3602
+ getLicenseKey() {
3603
+ if (this.config.licenseKey) {
3604
+ return this.config.licenseKey;
3605
+ }
3606
+ const envVar = this.config.licenseKeyEnvVar || "NICE2DEV_LICENSE_KEY";
3607
+ return process.env[envVar] || null;
3608
+ }
3609
+ /** Log message (unless silent) */
3610
+ log(message, type = "info") {
3611
+ if (this.config.silent) {
3612
+ return;
3613
+ }
3614
+ const prefix = "[nice2dev/license]";
3615
+ switch (type) {
3616
+ case "warn":
3617
+ console.warn(`${prefix} ⚠ ${message}`);
3618
+ break;
3619
+ case "error":
3620
+ console.error(`${prefix} ✗ ${message}`);
3621
+ break;
3622
+ default:
3623
+ console.log(`${prefix} ${message}`);
3624
+ }
3625
+ }
3626
+ /** Validate license online */
3627
+ async validateOnline(licenseKey) {
3628
+ if (!this.config.licenseServerUrl) {
3629
+ return null;
3630
+ }
3631
+ const endpoint = this.config.validationEndpoint || "/api/licenses/validate";
3632
+ const url = `${this.config.licenseServerUrl}${endpoint}`;
3633
+ try {
3634
+ const controller = new AbortController();
3635
+ const timeout = setTimeout(() => controller.abort(), this.config.validationTimeout);
3636
+ const response = await fetch(url, {
3637
+ method: "POST",
3638
+ headers: { "Content-Type": "application/json" },
3639
+ body: JSON.stringify({ licenseKey }),
3640
+ signal: controller.signal
3641
+ });
3642
+ clearTimeout(timeout);
3643
+ if (!response.ok) {
3644
+ this.log(`Online validation failed: ${response.status}`, "warn");
3645
+ return null;
3646
+ }
3647
+ const result = await response.json();
3648
+ return result.license || null;
3649
+ } catch (error) {
3650
+ this.log(`Online validation error: ${error.message}`, "warn");
3651
+ return null;
3652
+ }
3653
+ }
3654
+ /** Load cached license */
3655
+ async loadCachedLicense(licenseKey) {
3656
+ if (!this.config.allowOffline || !this.config.cacheDir) {
3657
+ return null;
3658
+ }
3659
+ const fs = await Promise.resolve().then(() => require("./__vite-browser-external-DES75WN9.cjs")).then((m) => m.promises).catch(() => null);
3660
+ if (!fs) {
3661
+ return null;
3662
+ }
3663
+ const cacheFile = `${this.config.cacheDir}/.license-cache.json`;
3664
+ try {
3665
+ const content = await fs.readFile(cacheFile, "utf-8");
3666
+ const cached = JSON.parse(content);
3667
+ if (cached.licenseKey === licenseKey && cached.license) {
3668
+ return cached.license;
3669
+ }
3670
+ } catch {
3671
+ }
3672
+ return null;
3673
+ }
3674
+ /** Save license to cache */
3675
+ async saveLicenseCache(licenseKey, license) {
3676
+ if (!this.config.cacheDir) {
3677
+ return;
3678
+ }
3679
+ const fs = await Promise.resolve().then(() => require("./__vite-browser-external-DES75WN9.cjs")).then((m) => m.promises).catch(() => null);
3680
+ const path = await Promise.resolve().then(() => require("./__vite-browser-external-DES75WN9.cjs")).catch(() => null);
3681
+ if (!fs || !path) {
3682
+ return;
3683
+ }
3684
+ try {
3685
+ await fs.mkdir(this.config.cacheDir, { recursive: true });
3686
+ const cacheFile = `${this.config.cacheDir}/.license-cache.json`;
3687
+ await fs.writeFile(
3688
+ cacheFile,
3689
+ JSON.stringify({ licenseKey, license, cachedAt: (/* @__PURE__ */ new Date()).toISOString() })
3690
+ );
3691
+ } catch {
3692
+ }
3693
+ }
3694
+ /** Validate license (main entry point) */
3695
+ async validate() {
3696
+ var _a;
3697
+ const result = {
3698
+ valid: false,
3699
+ warnings: [],
3700
+ errors: []
3701
+ };
3702
+ const licenseKey = this.getLicenseKey();
3703
+ if (!licenseKey) {
3704
+ if (this.config.failOnInvalid) {
3705
+ result.errors.push(
3706
+ "No license key provided. Set NICE2DEV_LICENSE_KEY or pass licenseKey option."
3707
+ );
3708
+ } else {
3709
+ result.warnings.push("No license key provided. Some features may be disabled.");
3710
+ result.valid = true;
3711
+ }
3712
+ this.validationResult = result;
3713
+ return result;
3714
+ }
3715
+ if (!FeatureGate.validateKeyFormat(licenseKey)) {
3716
+ result.errors.push("Invalid license key format.");
3717
+ this.validationResult = result;
3718
+ return result;
3719
+ }
3720
+ const metadata = FeatureGate.extractKeyMetadata(licenseKey);
3721
+ result.licenseKey = licenseKey;
3722
+ result.tier = metadata == null ? void 0 : metadata.tier;
3723
+ let license = await this.validateOnline(licenseKey);
3724
+ if (!license && this.config.allowOffline) {
3725
+ license = await this.loadCachedLicense(licenseKey);
3726
+ if (license) {
3727
+ result.warnings.push("Using cached license (offline mode).");
3728
+ }
3729
+ }
3730
+ if (!license) {
3731
+ this.log("Could not validate license online, using key metadata.", "warn");
3732
+ result.warnings.push("License could not be validated online.");
3733
+ if (this.config.requiredTier && (metadata == null ? void 0 : metadata.tier)) {
3734
+ if (!FeatureGate.tierMeetsRequirement(metadata.tier, this.config.requiredTier)) {
3735
+ result.errors.push(
3736
+ `License tier "${metadata.tier}" does not meet required tier "${this.config.requiredTier}".`
3737
+ );
3738
+ this.validationResult = result;
3739
+ return result;
3740
+ }
3741
+ }
3742
+ result.valid = !this.config.failOnInvalid;
3743
+ this.validationResult = result;
3744
+ return result;
3745
+ }
3746
+ await this.saveLicenseCache(licenseKey, license);
3747
+ this.licenseInfo = license;
3748
+ result.licensee = license.licensee;
3749
+ result.tier = license.tier;
3750
+ result.features = license.features;
3751
+ result.expiresAt = license.expiresAt || void 0;
3752
+ if (license.expiresAt) {
3753
+ const expiresAt = new Date(license.expiresAt);
3754
+ const now = /* @__PURE__ */ new Date();
3755
+ const daysRemaining = Math.ceil(
3756
+ (expiresAt.getTime() - now.getTime()) / (1e3 * 60 * 60 * 24)
3757
+ );
3758
+ result.daysRemaining = daysRemaining;
3759
+ if (daysRemaining <= 0) {
3760
+ if (this.config.failOnExpired) {
3761
+ result.errors.push("License has expired.");
3762
+ this.validationResult = result;
3763
+ return result;
3764
+ } else {
3765
+ result.warnings.push("License has expired. Some features may be disabled.");
3766
+ }
3767
+ } else if (daysRemaining <= 30) {
3768
+ result.warnings.push(`License expires in ${daysRemaining} days.`);
3769
+ }
3770
+ }
3771
+ if (this.config.requiredTier) {
3772
+ if (!FeatureGate.tierMeetsRequirement(license.tier, this.config.requiredTier)) {
3773
+ result.errors.push(
3774
+ `License tier "${license.tier}" does not meet required tier "${this.config.requiredTier}".`
3775
+ );
3776
+ this.validationResult = result;
3777
+ return result;
3778
+ }
3779
+ }
3780
+ if ((_a = this.config.requiredFeatures) == null ? void 0 : _a.length) {
3781
+ const missingFeatures = this.config.requiredFeatures.filter(
3782
+ (f) => !license.features.includes(f)
3783
+ );
3784
+ if (missingFeatures.length > 0) {
3785
+ result.errors.push(`License missing required features: ${missingFeatures.join(", ")}`);
3786
+ this.validationResult = result;
3787
+ return result;
3788
+ }
3789
+ }
3790
+ result.valid = true;
3791
+ this.validationResult = result;
3792
+ this.log(`License validated: ${license.licensee} (${license.tier})`);
3793
+ return result;
3794
+ }
3795
+ /** Get validation result */
3796
+ getResult() {
3797
+ return this.validationResult;
3798
+ }
3799
+ /** Get license info */
3800
+ getLicenseInfo() {
3801
+ return this.licenseInfo;
3802
+ }
3803
+ /** Check if feature is available */
3804
+ hasFeature(featureId) {
3805
+ var _a;
3806
+ return ((_a = this.licenseInfo) == null ? void 0 : _a.features.includes(featureId)) ?? false;
3807
+ }
3808
+ /** Check if tier meets requirement */
3809
+ meetsTier(requiredTier) {
3810
+ return this.licenseInfo ? FeatureGate.tierMeetsRequirement(this.licenseInfo.tier, requiredTier) : false;
3811
+ }
3812
+ }
3813
+ class Nice2DevLicenseWebpackPlugin {
3814
+ constructor(options = {}) {
3815
+ this.validated = false;
3816
+ this.options = options;
3817
+ this.validator = new BuildLicenseValidator(options);
3818
+ }
3819
+ apply(compiler) {
3820
+ compiler.hooks.beforeRun.tapPromise("Nice2DevLicensePlugin", async () => {
3821
+ if (this.validated) {
3822
+ return;
3823
+ }
3824
+ const result = await this.validator.validate();
3825
+ this.validated = true;
3826
+ result.warnings.forEach((w) => console.warn(`[nice2dev] ⚠ ${w}`));
3827
+ if (result.errors.length > 0 && this.options.failOnInvalid !== false) {
3828
+ result.errors.forEach((e) => console.error(`[nice2dev] ✗ ${e}`));
3829
+ throw new Error("License validation failed");
3830
+ }
3831
+ });
3832
+ compiler.hooks.compilation.tap("Nice2DevLicensePlugin", (compilation) => {
3833
+ var _a, _b;
3834
+ const DefinePlugin = ((_a = compiler.webpack) == null ? void 0 : _a.DefinePlugin) || require("webpack").DefinePlugin;
3835
+ const license = this.validator.getLicenseInfo();
3836
+ new DefinePlugin({
3837
+ "process.env.NICE2DEV_LICENSE_VALID": JSON.stringify(
3838
+ ((_b = this.validator.getResult()) == null ? void 0 : _b.valid) ?? false
3839
+ ),
3840
+ "process.env.NICE2DEV_LICENSE_TIER": JSON.stringify((license == null ? void 0 : license.tier) ?? "trial"),
3841
+ "process.env.NICE2DEV_LICENSE_FEATURES": JSON.stringify((license == null ? void 0 : license.features) ?? [])
3842
+ }).apply(compiler);
3843
+ });
3844
+ }
3845
+ }
3846
+ function nice2devLicenseVitePlugin(options = {}) {
3847
+ const validator = new BuildLicenseValidator(options);
3848
+ let validated = false;
3849
+ return {
3850
+ name: "nice2dev-license",
3851
+ async buildStart() {
3852
+ if (validated) {
3853
+ return;
3854
+ }
3855
+ const result = await validator.validate();
3856
+ validated = true;
3857
+ result.warnings.forEach((w) => this.warn(w));
3858
+ if (result.errors.length > 0 && options.failOnInvalid !== false) {
3859
+ result.errors.forEach((e) => this.error(e));
3860
+ throw new Error("License validation failed");
3861
+ }
3862
+ },
3863
+ config() {
3864
+ var _a;
3865
+ const license = validator.getLicenseInfo();
3866
+ return {
3867
+ define: {
3868
+ "import.meta.env.NICE2DEV_LICENSE_VALID": JSON.stringify(
3869
+ ((_a = validator.getResult()) == null ? void 0 : _a.valid) ?? false
3870
+ ),
3871
+ "import.meta.env.NICE2DEV_LICENSE_TIER": JSON.stringify((license == null ? void 0 : license.tier) ?? "trial"),
3872
+ "import.meta.env.NICE2DEV_LICENSE_FEATURES": JSON.stringify((license == null ? void 0 : license.features) ?? [])
3873
+ }
3874
+ };
3875
+ }
3876
+ };
3877
+ }
3878
+ function nice2devLicenseRollupPlugin(options = {}) {
3879
+ const validator = new BuildLicenseValidator(options);
3880
+ let validated = false;
3881
+ return {
3882
+ name: "nice2dev-license",
3883
+ async buildStart() {
3884
+ if (validated) {
3885
+ return;
3886
+ }
3887
+ const result = await validator.validate();
3888
+ validated = true;
3889
+ result.warnings.forEach((w) => this.warn(w));
3890
+ if (result.errors.length > 0 && options.failOnInvalid !== false) {
3891
+ throw new Error(result.errors.join("\n"));
3892
+ }
3893
+ }
3894
+ };
3895
+ }
3896
+ function detectCIPlatform() {
3897
+ const env = process.env;
3898
+ if (env.GITHUB_ACTIONS === "true") {
3899
+ return "github-actions";
3900
+ }
3901
+ if (env.TF_BUILD === "True") {
3902
+ return "azure-devops";
3903
+ }
3904
+ if (env.GITLAB_CI === "true") {
3905
+ return "gitlab-ci";
3906
+ }
3907
+ if (env.JENKINS_URL) {
3908
+ return "jenkins";
3909
+ }
3910
+ if (env.CIRCLECI === "true") {
3911
+ return "circleci";
3912
+ }
3913
+ if (env.BITBUCKET_BUILD_NUMBER) {
3914
+ return "bitbucket-pipelines";
3915
+ }
3916
+ if (env.TRAVIS === "true") {
3917
+ return "travis-ci";
3918
+ }
3919
+ return "unknown";
3920
+ }
3921
+ function isForkPR() {
3922
+ const env = process.env;
3923
+ const platform = detectCIPlatform();
3924
+ switch (platform) {
3925
+ case "github-actions":
3926
+ return env.GITHUB_EVENT_NAME === "pull_request" && env.GITHUB_HEAD_REF !== void 0 && env.GITHUB_REPOSITORY !== env.GITHUB_HEAD_REPOSITORY;
3927
+ case "gitlab-ci":
3928
+ return env.CI_MERGE_REQUEST_SOURCE_PROJECT_PATH !== env.CI_PROJECT_PATH;
3929
+ case "azure-devops":
3930
+ return env.BUILD_REASON === "PullRequest" && env.SYSTEM_PULLREQUEST_ISFORK === "True";
3931
+ default:
3932
+ return false;
3933
+ }
3934
+ }
3935
+ class CIOutputFormatter {
3936
+ constructor(platform, format = "human") {
3937
+ this.platform = platform;
3938
+ this.outputFormat = format;
3939
+ }
3940
+ info(message) {
3941
+ if (this.outputFormat === "json") {
3942
+ return;
3943
+ }
3944
+ console.log(`ℹ️ ${message}`);
3945
+ }
3946
+ success(message) {
3947
+ if (this.outputFormat === "json") {
3948
+ return;
3949
+ }
3950
+ switch (this.platform) {
3951
+ case "github-actions":
3952
+ console.log(`::notice::${message}`);
3953
+ break;
3954
+ case "azure-devops":
3955
+ console.log(`##[section]${message}`);
3956
+ break;
3957
+ default:
3958
+ console.log(`✅ ${message}`);
3959
+ }
3960
+ }
3961
+ warning(message) {
3962
+ if (this.outputFormat === "json") {
3963
+ return;
3964
+ }
3965
+ switch (this.platform) {
3966
+ case "github-actions":
3967
+ console.log(`::warning::${message}`);
3968
+ break;
3969
+ case "azure-devops":
3970
+ console.log(`##[warning]${message}`);
3971
+ break;
3972
+ case "gitlab-ci":
3973
+ console.warn(`\x1B[33mWarning: ${message}\x1B[0m`);
3974
+ break;
3975
+ default:
3976
+ console.warn(`⚠️ ${message}`);
3977
+ }
3978
+ }
3979
+ error(message) {
3980
+ if (this.outputFormat === "json") {
3981
+ return;
3982
+ }
3983
+ switch (this.platform) {
3984
+ case "github-actions":
3985
+ console.log(`::error::${message}`);
3986
+ break;
3987
+ case "azure-devops":
3988
+ console.log(`##[error]${message}`);
3989
+ break;
3990
+ case "gitlab-ci":
3991
+ console.error(`\x1B[31mError: ${message}\x1B[0m`);
3992
+ break;
3993
+ default:
3994
+ console.error(`❌ ${message}`);
3995
+ }
3996
+ }
3997
+ setOutput(name, value) {
3998
+ switch (this.platform) {
3999
+ case "github-actions":
4000
+ const fs = require("fs");
4001
+ const outputFile = process.env.GITHUB_OUTPUT;
4002
+ if (outputFile) {
4003
+ fs.appendFileSync(outputFile, `${name}=${value}
4004
+ `);
4005
+ }
4006
+ break;
4007
+ case "azure-devops":
4008
+ console.log(`##vso[task.setvariable variable=${name}]${value}`);
4009
+ break;
4010
+ }
4011
+ }
4012
+ setEnvVar(name, value) {
4013
+ switch (this.platform) {
4014
+ case "github-actions":
4015
+ const fs = require("fs");
4016
+ const envFile = process.env.GITHUB_ENV;
4017
+ if (envFile) {
4018
+ fs.appendFileSync(envFile, `${name}=${value}
4019
+ `);
4020
+ }
4021
+ break;
4022
+ case "azure-devops":
4023
+ console.log(`##vso[task.setvariable variable=${name}]${value}`);
4024
+ break;
4025
+ }
4026
+ }
4027
+ group(name, content) {
4028
+ switch (this.platform) {
4029
+ case "github-actions":
4030
+ console.log(`::group::${name}`);
4031
+ content();
4032
+ console.log("::endgroup::");
4033
+ break;
4034
+ case "azure-devops":
4035
+ console.log(`##[group]${name}`);
4036
+ content();
4037
+ console.log("##[endgroup]");
4038
+ break;
4039
+ default:
4040
+ console.log(`
4041
+ --- ${name} ---`);
4042
+ content();
4043
+ console.log("");
4044
+ }
4045
+ }
4046
+ }
4047
+ async function validateLicenseOnline(licenseKey, serverUrl) {
4048
+ try {
4049
+ const response = await fetch(`${serverUrl}/api/licenses/validate`, {
4050
+ method: "POST",
4051
+ headers: { "Content-Type": "application/json" },
4052
+ body: JSON.stringify({ licenseKey, context: "ci" })
4053
+ });
4054
+ if (!response.ok) {
4055
+ return null;
4056
+ }
4057
+ const data = await response.json();
4058
+ return data.license || null;
4059
+ } catch {
4060
+ return null;
4061
+ }
4062
+ }
4063
+ async function runCILicenseCheck(options = {}) {
4064
+ var _a;
4065
+ const platform = detectCIPlatform();
4066
+ const formatter = new CIOutputFormatter(platform, options.outputFormat);
4067
+ const result = {
4068
+ success: false,
4069
+ platform,
4070
+ exitCode: 0,
4071
+ message: "",
4072
+ warnings: [],
4073
+ errors: []
4074
+ };
4075
+ if (options.skipForForks && isForkPR()) {
4076
+ formatter.info("Skipping license check for fork PR");
4077
+ result.success = true;
4078
+ result.message = "License check skipped for fork PR";
4079
+ return result;
4080
+ }
4081
+ const licenseKey = options.licenseKey || process.env[options.licenseKeyEnvVar || "NICE2DEV_LICENSE_KEY"];
4082
+ if (!licenseKey) {
4083
+ const error = "No license key found. Set NICE2DEV_LICENSE_KEY secret or pass licenseKey option.";
4084
+ formatter.error(error);
4085
+ result.errors.push(error);
4086
+ result.message = error;
4087
+ result.exitCode = options.failOnInvalid !== false ? 1 : 0;
4088
+ result.success = options.failOnInvalid === false;
4089
+ return result;
4090
+ }
4091
+ formatter.info(`Validating license: ${licenseKey.substring(0, 12)}...`);
4092
+ if (!FeatureGate.validateKeyFormat(licenseKey)) {
4093
+ const error = "Invalid license key format";
4094
+ formatter.error(error);
4095
+ result.errors.push(error);
4096
+ result.message = error;
4097
+ result.exitCode = 1;
4098
+ return result;
4099
+ }
4100
+ const metadata = FeatureGate.extractKeyMetadata(licenseKey);
4101
+ const license = {
4102
+ key: licenseKey,
4103
+ tier: (metadata == null ? void 0 : metadata.tier) || "trial",
4104
+ features: [],
4105
+ licensee: void 0,
4106
+ expiresAt: void 0,
4107
+ daysRemaining: void 0
4108
+ };
4109
+ result.license = license;
4110
+ if (options.licenseServerUrl) {
4111
+ const onlineLicense = await validateLicenseOnline(licenseKey, options.licenseServerUrl);
4112
+ if (onlineLicense) {
4113
+ license.licensee = onlineLicense.licensee;
4114
+ license.tier = onlineLicense.tier;
4115
+ license.features = onlineLicense.features;
4116
+ license.expiresAt = onlineLicense.expiresAt || void 0;
4117
+ formatter.success(`License validated for: ${onlineLicense.licensee} (${onlineLicense.tier})`);
4118
+ } else {
4119
+ const warning = "Could not validate license online, using key metadata";
4120
+ formatter.warning(warning);
4121
+ result.warnings.push(warning);
4122
+ }
4123
+ }
4124
+ if (license.expiresAt) {
4125
+ const expiresAt = new Date(license.expiresAt);
4126
+ const now = /* @__PURE__ */ new Date();
4127
+ const daysRemaining = Math.ceil((expiresAt.getTime() - now.getTime()) / (1e3 * 60 * 60 * 24));
4128
+ license.daysRemaining = daysRemaining;
4129
+ const graceDays = options.expirationGraceDays ?? 0;
4130
+ if (daysRemaining < -graceDays) {
4131
+ const error = `License expired ${Math.abs(daysRemaining)} days ago`;
4132
+ if (options.failOnExpired !== false) {
4133
+ formatter.error(error);
4134
+ result.errors.push(error);
4135
+ result.exitCode = 1;
4136
+ result.message = error;
4137
+ return result;
4138
+ } else {
4139
+ formatter.warning(error);
4140
+ result.warnings.push(error);
4141
+ }
4142
+ } else if (daysRemaining <= 0) {
4143
+ const warning = `License expired but within grace period (${graceDays} days)`;
4144
+ formatter.warning(warning);
4145
+ result.warnings.push(warning);
4146
+ } else if (daysRemaining <= 30) {
4147
+ const warning = `License expires in ${daysRemaining} days`;
4148
+ formatter.warning(warning);
4149
+ result.warnings.push(warning);
4150
+ }
4151
+ }
4152
+ if (options.requiredTier) {
4153
+ if (!FeatureGate.tierMeetsRequirement(license.tier, options.requiredTier)) {
4154
+ const error = `License tier "${license.tier}" does not meet required tier "${options.requiredTier}"`;
4155
+ formatter.error(error);
4156
+ result.errors.push(error);
4157
+ result.exitCode = 1;
4158
+ result.message = error;
4159
+ return result;
4160
+ }
4161
+ formatter.info(`Tier check passed: ${license.tier} >= ${options.requiredTier}`);
4162
+ }
4163
+ if ((_a = options.requiredFeatures) == null ? void 0 : _a.length) {
4164
+ const missingFeatures = options.requiredFeatures.filter((f) => !license.features.includes(f));
4165
+ if (missingFeatures.length > 0) {
4166
+ const error = `Missing required features: ${missingFeatures.join(", ")}`;
4167
+ formatter.error(error);
4168
+ result.errors.push(error);
4169
+ result.exitCode = 1;
4170
+ result.message = error;
4171
+ return result;
4172
+ }
4173
+ formatter.info(`Feature check passed: ${options.requiredFeatures.join(", ")}`);
4174
+ }
4175
+ if (options.setEnvVars !== false) {
4176
+ formatter.setEnvVar("NICE2DEV_LICENSE_VALID", "true");
4177
+ formatter.setEnvVar("NICE2DEV_LICENSE_TIER", license.tier);
4178
+ formatter.setOutput("license-valid", "true");
4179
+ formatter.setOutput("license-tier", license.tier);
4180
+ }
4181
+ result.success = true;
4182
+ result.message = `License validated successfully (${license.tier})`;
4183
+ formatter.success(result.message);
4184
+ if (options.outputFormat === "json") {
4185
+ console.log(JSON.stringify(result, null, 2));
4186
+ }
4187
+ return result;
4188
+ }
4189
+ function generateGitHubActionsWorkflow(options) {
4190
+ return `name: License Check
4191
+
4192
+ on:
4193
+ pull_request:
4194
+ push:
4195
+ branches: [main, master]
4196
+
4197
+ jobs:
4198
+ license-check:
4199
+ runs-on: ubuntu-latest
4200
+ steps:
4201
+ - uses: actions/checkout@v4
4202
+
4203
+ - name: Setup Node.js
4204
+ uses: actions/setup-node@v4
4205
+ with:
4206
+ node-version: '20'
4207
+
4208
+ - name: Install dependencies
4209
+ run: npm ci
4210
+
4211
+ - name: Validate License
4212
+ run: npx nice2dev-license-check${options.requiredTier ? ` -t ${options.requiredTier}` : ""}${options.skipForForks ? " --skip-for-forks" : ""}
4213
+ env:
4214
+ NICE2DEV_LICENSE_KEY: \${{ secrets.NICE2DEV_LICENSE_KEY }}
4215
+ `;
4216
+ }
4217
+ function generateAzureDevOpsPipeline(options) {
4218
+ return `trigger:
4219
+ - main
4220
+ - master
4221
+
4222
+ pool:
4223
+ vmImage: 'ubuntu-latest'
4224
+
4225
+ steps:
4226
+ - task: NodeTool@0
4227
+ inputs:
4228
+ versionSpec: '20.x'
4229
+ displayName: 'Install Node.js'
4230
+
4231
+ - script: npm ci
4232
+ displayName: 'Install dependencies'
4233
+
4234
+ - script: npx nice2dev-license-check${options.requiredTier ? ` -t ${options.requiredTier}` : ""}
4235
+ displayName: 'Validate License'
4236
+ env:
4237
+ NICE2DEV_LICENSE_KEY: $(NICE2DEV_LICENSE_KEY)
4238
+ `;
4239
+ }
4240
+ function generateGitLabCIPipeline(options) {
4241
+ return `stages:
4242
+ - validate
4243
+
4244
+ license-check:
4245
+ stage: validate
4246
+ image: node:20
4247
+ script:
4248
+ - npm ci
4249
+ - npx nice2dev-license-check${options.requiredTier ? ` -t ${options.requiredTier}` : ""}
4250
+ variables:
4251
+ NICE2DEV_LICENSE_KEY: $NICE2DEV_LICENSE_KEY
4252
+ `;
4253
+ }
4254
+ const VERSION = "1.1.0";
4255
+ exports.LicenseValidator = FeatureGate.LicenseValidator;
4256
+ exports.checkFeatureAccess = FeatureGate.checkFeatureAccess;
4257
+ exports.collectFingerprintData = FeatureGate.collectFingerprintData;
4258
+ exports.compareFingerprintsData = FeatureGate.compareFingerprintsData;
4259
+ exports.compareTiers = FeatureGate.compareTiers;
4260
+ exports.extractKeyMetadata = FeatureGate.extractKeyMetadata;
4261
+ exports.generateFingerprint = FeatureGate.generateFingerprint;
4262
+ exports.generateLicenseInfo = FeatureGate.generateLicenseInfo;
4263
+ exports.generateLicenseKey = FeatureGate.generateLicenseKey;
4264
+ exports.generateMachineId = FeatureGate.generateMachineId;
4265
+ exports.getAllFeatures = FeatureGate.getAllFeatures;
4266
+ exports.getAllPlans = FeatureGate.getAllPlans;
4267
+ exports.getAvailableFeatures = FeatureGate.getAvailableFeatures;
4268
+ exports.getDefaultFeatures = FeatureGate.getDefaultFeatures;
4269
+ exports.getDefaultMachines = FeatureGate.getDefaultMachines;
4270
+ exports.getDefaultSeats = FeatureGate.getDefaultSeats;
4271
+ exports.getFeature = FeatureGate.getFeature;
4272
+ exports.getFeaturesByCategory = FeatureGate.getFeaturesByCategory;
4273
+ exports.getMissingFeatures = FeatureGate.getMissingFeatures;
4274
+ exports.getOrGenerateFingerprint = FeatureGate.getOrGenerateFingerprint;
4275
+ exports.getPlan = FeatureGate.getPlan;
4276
+ exports.getStoredFingerprint = FeatureGate.getStoredFingerprint;
4277
+ exports.getValidator = FeatureGate.getValidator;
4278
+ exports.hasFeature = FeatureGate.hasFeature;
4279
+ exports.hasTier = FeatureGate.hasTier;
4280
+ exports.initializeDefaults = FeatureGate.initializeDefaults;
4281
+ exports.isFeatureLicensed = FeatureGate.isFeatureLicensed;
4282
+ exports.maskLicenseKey = FeatureGate.maskLicenseKey;
4283
+ exports.normalizeLicenseKey = FeatureGate.normalizeLicenseKey;
4284
+ exports.registerFeature = FeatureGate.registerFeature;
4285
+ exports.registerFeatures = FeatureGate.registerFeatures;
4286
+ exports.registerPlan = FeatureGate.registerPlan;
4287
+ exports.registerPlans = FeatureGate.registerPlans;
4288
+ exports.storeFingerprint = FeatureGate.storeFingerprint;
4289
+ exports.tierMeetsRequirement = FeatureGate.tierMeetsRequirement;
4290
+ exports.validateKeyFormat = FeatureGate.validateKeyFormat;
4291
+ exports.validateLicense = FeatureGate.validateLicense;
4292
+ exports.ActivationService = ActivationService;
4293
+ exports.ActivationWizard = ActivationWizard;
4294
+ exports.ApiRateLimiter = ApiRateLimiter;
4295
+ exports.AuditTrail = AuditTrail;
4296
+ exports.BillingService = BillingService;
4297
+ exports.BuildLicenseValidator = BuildLicenseValidator;
4298
+ exports.DEFAULT_RATE_LIMITS = DEFAULT_RATE_LIMITS;
4299
+ exports.DEFAULT_REFUND_POLICY = DEFAULT_REFUND_POLICY;
4300
+ exports.DEFAULT_SLA_CONFIGS = DEFAULT_SLA_CONFIGS;
4301
+ exports.FloatingLicenseManager = FloatingLicenseManager;
4302
+ exports.LicenseCard = LicenseCard;
4303
+ exports.LicensePortal = LicensePortal;
4304
+ exports.LicensePortalService = LicensePortalService;
4305
+ exports.LicenseStatusBadge = LicenseStatusBadge;
4306
+ exports.LicenseTierBadge = LicenseTierBadge;
4307
+ exports.LicenseTransfer = LicenseTransfer;
4308
+ exports.LicenseTransferService = LicenseTransferService;
4309
+ exports.Nice2DevLicenseWebpackPlugin = Nice2DevLicenseWebpackPlugin;
4310
+ exports.RateLimiter = RateLimiter;
4311
+ exports.RefundService = RefundService;
4312
+ exports.SeatManager = SeatManager;
4313
+ exports.SlaTracker = SlaTracker;
4314
+ exports.TransferStatusBadge = TransferStatusBadge;
4315
+ exports.UsageAnalyticsDashboard = UsageAnalyticsDashboard;
4316
+ exports.UsageAnalyticsService = UsageAnalyticsService;
4317
+ exports.UsageTelemetry = UsageTelemetry;
4318
+ exports.VERSION = VERSION;
4319
+ exports.WhiteLabelManager = WhiteLabelManager;
4320
+ exports.createApiRateLimiter = createApiRateLimiter;
4321
+ exports.createAuditTrail = createAuditTrail;
4322
+ exports.createBillingService = createBillingService;
4323
+ exports.createFloatingLicenseManager = createFloatingLicenseManager;
4324
+ exports.createRefundService = createRefundService;
4325
+ exports.createSeatManager = createSeatManager;
4326
+ exports.createSlaTracker = createSlaTracker;
4327
+ exports.createTelemetry = createTelemetry;
4328
+ exports.createWhiteLabelManager = createWhiteLabelManager;
4329
+ exports.detectCIPlatform = detectCIPlatform;
4330
+ exports.generateAzureDevOpsPipeline = generateAzureDevOpsPipeline;
4331
+ exports.generateBundleTags = generateBundleTags;
4332
+ exports.generateGitHubActionsWorkflow = generateGitHubActionsWorkflow;
4333
+ exports.generateGitLabCIPipeline = generateGitLabCIPipeline;
4334
+ exports.generateProtectionManifest = generateProtectionManifest;
4335
+ exports.getAccessibleModules = getAccessibleModules;
4336
+ exports.getAllProtectedModules = getAllProtectedModules;
4337
+ exports.getInaccessibleModules = getInaccessibleModules;
4338
+ exports.getProtectedModule = getProtectedModule;
4339
+ exports.hashKeyForAudit = hashKeyForAudit;
4340
+ exports.hashLicenseKey = hashLicenseKey;
4341
+ exports.isForkPR = isForkPR;
4342
+ exports.isModuleAccessible = isModuleAccessible;
4343
+ exports.nice2devLicenseRollupPlugin = nice2devLicenseRollupPlugin;
4344
+ exports.nice2devLicenseVitePlugin = nice2devLicenseVitePlugin;
4345
+ exports.registerProtectedModule = registerProtectedModule;
4346
+ exports.registerProtectedModules = registerProtectedModules;
4347
+ exports.runCILicenseCheck = runCILicenseCheck;
4348
+ exports.useActivationWizard = useActivationWizard;
4349
+ exports.useLicensePortal = useLicensePortal;
4350
+ exports.useLicenseTransfer = useLicenseTransfer;
4351
+ exports.useUsageAnalytics = useUsageAnalytics;
4352
+ //# sourceMappingURL=index.cjs.map