astro-tractstack 2.2.3 → 2.2.5
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/package.json
CHANGED
|
@@ -8,6 +8,7 @@ import DashboardActivity from './Dashboard_Activity';
|
|
|
8
8
|
import SankeyDiagram from '../codehooks/SankeyDiagram';
|
|
9
9
|
import EpinetDurationSelector from '../codehooks/EpinetDurationSelector';
|
|
10
10
|
import FetchAnalytics from './state/FetchAnalytics';
|
|
11
|
+
import type { PulseMetrics, SystemMetrics } from './state/FetchAnalytics';
|
|
11
12
|
import type { FullContentMapItem } from '@/types/tractstack';
|
|
12
13
|
|
|
13
14
|
interface StoryKeepDashboardAnalyticsProps {
|
|
@@ -15,12 +16,16 @@ interface StoryKeepDashboardAnalyticsProps {
|
|
|
15
16
|
initializing?: boolean;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
interface ErrorBoundaryProps {
|
|
20
|
+
children: ReactNode;
|
|
21
|
+
fallback: ReactNode;
|
|
22
|
+
}
|
|
23
|
+
|
|
19
24
|
class ErrorBoundary extends Component<
|
|
20
|
-
|
|
25
|
+
ErrorBoundaryProps,
|
|
21
26
|
{ hasError: boolean }
|
|
22
27
|
> {
|
|
23
|
-
constructor(props:
|
|
28
|
+
constructor(props: ErrorBoundaryProps) {
|
|
24
29
|
super(props);
|
|
25
30
|
this.state = { hasError: false };
|
|
26
31
|
}
|
|
@@ -37,6 +42,70 @@ class ErrorBoundary extends Component<
|
|
|
37
42
|
}
|
|
38
43
|
}
|
|
39
44
|
|
|
45
|
+
// Types derived from Dashboard_Activity.tsx
|
|
46
|
+
interface ActivitySeries {
|
|
47
|
+
id: string;
|
|
48
|
+
data: Array<{ x: any; y: number }>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
interface DashboardData {
|
|
52
|
+
stats: {
|
|
53
|
+
daily: number;
|
|
54
|
+
weekly: number;
|
|
55
|
+
monthly: number;
|
|
56
|
+
};
|
|
57
|
+
line: ActivitySeries[];
|
|
58
|
+
dailyAnonymous: number;
|
|
59
|
+
dailyKnown: number;
|
|
60
|
+
weeklyAnonymous: number;
|
|
61
|
+
weeklyKnown: number;
|
|
62
|
+
monthlyAnonymous: number;
|
|
63
|
+
monthlyKnown: number;
|
|
64
|
+
status?: string;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
interface LeadsData {
|
|
68
|
+
totalLeads: number;
|
|
69
|
+
status?: string;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Types derived from SankeyDiagram.tsx
|
|
73
|
+
interface SankeyNode {
|
|
74
|
+
name: string;
|
|
75
|
+
id: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
interface SankeyLink {
|
|
79
|
+
source: number;
|
|
80
|
+
target: number;
|
|
81
|
+
value: number;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
interface EpinetData {
|
|
85
|
+
nodes: SankeyNode[];
|
|
86
|
+
links: SankeyLink[];
|
|
87
|
+
status?: string;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Types derived from EpinetDurationSelector.tsx usage
|
|
91
|
+
interface UserCountItem {
|
|
92
|
+
id: string;
|
|
93
|
+
count: number;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
interface AnalyticsState {
|
|
97
|
+
dashboard: DashboardData | null;
|
|
98
|
+
leads: LeadsData | null;
|
|
99
|
+
epinet: EpinetData | null;
|
|
100
|
+
userCounts: UserCountItem[];
|
|
101
|
+
hourlyNodeActivity: Record<string, number>;
|
|
102
|
+
pulse: PulseMetrics | null;
|
|
103
|
+
system: SystemMetrics | null;
|
|
104
|
+
isLoading: boolean;
|
|
105
|
+
status: string;
|
|
106
|
+
error: string | null;
|
|
107
|
+
}
|
|
108
|
+
|
|
40
109
|
const DurationSelector = ({
|
|
41
110
|
currentDurationHelper,
|
|
42
111
|
setStandardDuration,
|
|
@@ -83,22 +152,14 @@ export default function StoryKeepDashboard_Analytics({
|
|
|
83
152
|
const $epinetCustomFilters = useStore(epinetCustomFilters);
|
|
84
153
|
const [isDownloading, setIsDownloading] = useState(false);
|
|
85
154
|
|
|
86
|
-
|
|
87
|
-
const [analytics, setAnalytics] = useState<{
|
|
88
|
-
dashboard: any;
|
|
89
|
-
leads: any;
|
|
90
|
-
epinet: any;
|
|
91
|
-
userCounts: any[];
|
|
92
|
-
hourlyNodeActivity: any;
|
|
93
|
-
isLoading: boolean;
|
|
94
|
-
status: string;
|
|
95
|
-
error: string | null;
|
|
96
|
-
}>({
|
|
155
|
+
const [analytics, setAnalytics] = useState<AnalyticsState>({
|
|
97
156
|
dashboard: null,
|
|
98
157
|
leads: null,
|
|
99
158
|
epinet: null,
|
|
100
159
|
userCounts: [],
|
|
101
160
|
hourlyNodeActivity: {},
|
|
161
|
+
pulse: null,
|
|
162
|
+
system: null,
|
|
102
163
|
isLoading: false,
|
|
103
164
|
status: 'idle',
|
|
104
165
|
error: null,
|
|
@@ -128,7 +189,6 @@ export default function StoryKeepDashboard_Analytics({
|
|
|
128
189
|
});
|
|
129
190
|
};
|
|
130
191
|
|
|
131
|
-
// Duration helper for UI
|
|
132
192
|
const currentDurationHelper = useMemo(():
|
|
133
193
|
| 'daily'
|
|
134
194
|
| 'weekly'
|
|
@@ -151,7 +211,6 @@ export default function StoryKeepDashboard_Analytics({
|
|
|
151
211
|
return 'monthly';
|
|
152
212
|
}, [$epinetCustomFilters.startTimeUTC, $epinetCustomFilters.endTimeUTC]);
|
|
153
213
|
|
|
154
|
-
// Standard duration setter helper
|
|
155
214
|
const setStandardDuration = useCallback(
|
|
156
215
|
(newValue: 'daily' | 'weekly' | 'monthly') => {
|
|
157
216
|
const nowUTC = new Date();
|
|
@@ -171,7 +230,6 @@ export default function StoryKeepDashboard_Analytics({
|
|
|
171
230
|
[$epinetCustomFilters]
|
|
172
231
|
);
|
|
173
232
|
|
|
174
|
-
// Download leads CSV
|
|
175
233
|
const downloadLeadsCSV = async () => {
|
|
176
234
|
if (isDownloading) return;
|
|
177
235
|
try {
|
|
@@ -212,12 +270,10 @@ export default function StoryKeepDashboard_Analytics({
|
|
|
212
270
|
}
|
|
213
271
|
};
|
|
214
272
|
|
|
215
|
-
// Helper function for number formatting
|
|
216
273
|
const formatNumber = (num: number) => {
|
|
217
274
|
return new Intl.NumberFormat().format(num);
|
|
218
275
|
};
|
|
219
276
|
|
|
220
|
-
// Prepare stats data for display
|
|
221
277
|
const stats = [
|
|
222
278
|
{
|
|
223
279
|
name: 'Past 24 Hours',
|
|
@@ -263,6 +319,77 @@ export default function StoryKeepDashboard_Analytics({
|
|
|
263
319
|
</div>
|
|
264
320
|
)}
|
|
265
321
|
|
|
322
|
+
{/* Live Operations Row */}
|
|
323
|
+
<div className="mb-6 grid grid-cols-1 gap-4 md:grid-cols-3">
|
|
324
|
+
{/* Card 1: Active Visitors */}
|
|
325
|
+
<div className="rounded-lg border border-gray-100 bg-white px-4 py-3 shadow-sm transition-colors hover:border-cyan-100">
|
|
326
|
+
<div className="flex items-center gap-2">
|
|
327
|
+
<span className="relative flex h-2.5 w-2.5">
|
|
328
|
+
<span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-green-400 opacity-75"></span>
|
|
329
|
+
<span className="relative inline-flex h-2.5 w-2.5 rounded-full bg-green-500"></span>
|
|
330
|
+
</span>
|
|
331
|
+
<dt className="text-sm font-bold text-gray-800">Active Visitors</dt>
|
|
332
|
+
</div>
|
|
333
|
+
<dd className="mt-2">
|
|
334
|
+
<div className="text-2xl font-bold tracking-tight text-cyan-700">
|
|
335
|
+
{analytics.pulse
|
|
336
|
+
? formatNumber(analytics.pulse.activeVisitors)
|
|
337
|
+
: '-'}
|
|
338
|
+
</div>
|
|
339
|
+
<div className="mt-1 text-xs text-gray-600">
|
|
340
|
+
{analytics.pulse
|
|
341
|
+
? `${formatNumber(analytics.pulse.activeLeads)} Leads, ${formatNumber(analytics.pulse.activeGuests)} Guests`
|
|
342
|
+
: 'Live sessions'}
|
|
343
|
+
</div>
|
|
344
|
+
</dd>
|
|
345
|
+
</div>
|
|
346
|
+
|
|
347
|
+
{/* Card 2: Traffic Velocity */}
|
|
348
|
+
<div className="rounded-lg border border-gray-100 bg-white px-4 py-3 shadow-sm transition-colors hover:border-cyan-100">
|
|
349
|
+
<dt className="text-sm font-bold text-gray-800">Traffic Velocity</dt>
|
|
350
|
+
<dd className="mt-2">
|
|
351
|
+
<div className="text-2xl font-bold tracking-tight text-cyan-700">
|
|
352
|
+
{analytics.pulse ? formatNumber(analytics.pulse.velocity) : '-'}{' '}
|
|
353
|
+
<span className="text-sm font-normal text-gray-500">/ Hr</span>
|
|
354
|
+
</div>
|
|
355
|
+
<div className="mt-1 text-xs text-gray-600">
|
|
356
|
+
Events (Last Full Hour)
|
|
357
|
+
</div>
|
|
358
|
+
</dd>
|
|
359
|
+
</div>
|
|
360
|
+
|
|
361
|
+
{/* Card 3: System Health */}
|
|
362
|
+
<div className="rounded-lg border border-gray-100 bg-white px-4 py-3 shadow-sm transition-colors hover:border-cyan-100">
|
|
363
|
+
<dt className="text-sm font-bold text-gray-800">System Health</dt>
|
|
364
|
+
<dd className="mt-2">
|
|
365
|
+
{analytics.system ? (
|
|
366
|
+
<>
|
|
367
|
+
<div className="flex items-baseline gap-2">
|
|
368
|
+
<span
|
|
369
|
+
className={`text-2xl font-bold tracking-tight ${analytics.system.waitCount > 0 ? 'text-yellow-600' : 'text-green-600'}`}
|
|
370
|
+
>
|
|
371
|
+
{analytics.system.waitCount > 0 ? 'Busy' : 'Healthy'}
|
|
372
|
+
</span>
|
|
373
|
+
</div>
|
|
374
|
+
<div className="mt-1 text-xs text-gray-600">
|
|
375
|
+
DB Load:{' '}
|
|
376
|
+
{analytics.system.maxOpenConns > 0
|
|
377
|
+
? Math.round(
|
|
378
|
+
(analytics.system.openConns /
|
|
379
|
+
analytics.system.maxOpenConns) *
|
|
380
|
+
100
|
|
381
|
+
)
|
|
382
|
+
: 0}
|
|
383
|
+
%
|
|
384
|
+
</div>
|
|
385
|
+
</>
|
|
386
|
+
) : (
|
|
387
|
+
<div className="text-2xl font-bold text-gray-300">-</div>
|
|
388
|
+
)}
|
|
389
|
+
</dd>
|
|
390
|
+
</div>
|
|
391
|
+
</div>
|
|
392
|
+
|
|
266
393
|
{/* Stats Cards Grid */}
|
|
267
394
|
<div className="mb-6 grid grid-cols-3 gap-4">
|
|
268
395
|
{stats.map((item) => {
|
|
@@ -305,7 +432,6 @@ export default function StoryKeepDashboard_Analytics({
|
|
|
305
432
|
<hr className="my-1.5 border-gray-100 md:my-3.5" />
|
|
306
433
|
|
|
307
434
|
<dd>
|
|
308
|
-
{/* Desktop: side-by-side layout */}
|
|
309
435
|
<div className="hidden items-end justify-between md:flex">
|
|
310
436
|
<div className="flex-1">
|
|
311
437
|
<div className="text-sm text-gray-600">
|
|
@@ -327,7 +453,6 @@ export default function StoryKeepDashboard_Analytics({
|
|
|
327
453
|
</div>
|
|
328
454
|
</div>
|
|
329
455
|
|
|
330
|
-
{/* Mobile: stacked layout */}
|
|
331
456
|
<div className="md:hidden">
|
|
332
457
|
<div className="mb-1.5">
|
|
333
458
|
<div className="text-sm text-gray-600">
|
|
@@ -353,7 +478,6 @@ export default function StoryKeepDashboard_Analytics({
|
|
|
353
478
|
);
|
|
354
479
|
})}
|
|
355
480
|
|
|
356
|
-
{/* Total Leads Card */}
|
|
357
481
|
<div className="col-span-3 rounded-lg border border-gray-100 bg-white px-4 py-3 shadow-sm transition-colors hover:border-cyan-100">
|
|
358
482
|
<div className="flex items-center justify-between">
|
|
359
483
|
<dt className="text-sm font-bold text-gray-800">Total Leads</dt>
|
|
@@ -381,13 +505,11 @@ export default function StoryKeepDashboard_Analytics({
|
|
|
381
505
|
</div>
|
|
382
506
|
</div>
|
|
383
507
|
|
|
384
|
-
{/* Duration Selector */}
|
|
385
508
|
<DurationSelector
|
|
386
509
|
currentDurationHelper={currentDurationHelper}
|
|
387
510
|
setStandardDuration={setStandardDuration}
|
|
388
511
|
/>
|
|
389
512
|
|
|
390
|
-
{/* Dashboard Activity Chart */}
|
|
391
513
|
<div className="mb-6 overflow-hidden">
|
|
392
514
|
<h3 className="mb-4 text-lg font-bold text-gray-900">
|
|
393
515
|
Activity Over Time
|
|
@@ -412,12 +534,10 @@ export default function StoryKeepDashboard_Analytics({
|
|
|
412
534
|
)}
|
|
413
535
|
</div>
|
|
414
536
|
|
|
415
|
-
{/* Separator */}
|
|
416
537
|
<div className="my-8">
|
|
417
538
|
<hr className="border-gray-200" />
|
|
418
539
|
</div>
|
|
419
540
|
|
|
420
|
-
{/* User Journey Section */}
|
|
421
541
|
<div className="mb-6 overflow-visible">
|
|
422
542
|
<h3 className="mb-4 text-lg font-bold text-gray-900">
|
|
423
543
|
User Journey Analytics
|
|
@@ -466,7 +586,6 @@ export default function StoryKeepDashboard_Analytics({
|
|
|
466
586
|
analytics.isLoading || analytics.status === 'loading'
|
|
467
587
|
}
|
|
468
588
|
hourlyNodeActivity={analytics.hourlyNodeActivity}
|
|
469
|
-
// MODIFICATION: Read availableFilters from the store, not local state
|
|
470
589
|
availableFilters={$epinetCustomFilters.availableFilters}
|
|
471
590
|
appliedFilters={$epinetCustomFilters.appliedFilters}
|
|
472
591
|
onBeliefFilterChange={handleBeliefFilterChange}
|
|
@@ -485,7 +604,6 @@ export default function StoryKeepDashboard_Analytics({
|
|
|
485
604
|
analytics.isLoading || analytics.status === 'loading'
|
|
486
605
|
}
|
|
487
606
|
hourlyNodeActivity={analytics.hourlyNodeActivity}
|
|
488
|
-
// MODIFICATION: Read availableFilters from the store, not local state
|
|
489
607
|
availableFilters={$epinetCustomFilters.availableFilters}
|
|
490
608
|
appliedFilters={$epinetCustomFilters.appliedFilters}
|
|
491
609
|
onBeliefFilterChange={handleBeliefFilterChange}
|
|
@@ -502,7 +620,6 @@ export default function StoryKeepDashboard_Analytics({
|
|
|
502
620
|
fullContentMap={fullContentMap}
|
|
503
621
|
isLoading={analytics.isLoading || analytics.status === 'loading'}
|
|
504
622
|
hourlyNodeActivity={analytics.hourlyNodeActivity}
|
|
505
|
-
// MODIFICATION: Read availableFilters from the store, not local state
|
|
506
623
|
availableFilters={$epinetCustomFilters.availableFilters}
|
|
507
624
|
appliedFilters={$epinetCustomFilters.appliedFilters}
|
|
508
625
|
onBeliefFilterChange={handleBeliefFilterChange}
|
|
@@ -5,12 +5,31 @@ import { TractStackAPI } from '@/utils/api';
|
|
|
5
5
|
|
|
6
6
|
const VERBOSE = false;
|
|
7
7
|
|
|
8
|
+
export interface PulseMetrics {
|
|
9
|
+
activeVisitors: number;
|
|
10
|
+
activeLeads: number;
|
|
11
|
+
activeGuests: number;
|
|
12
|
+
velocity: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface SystemMetrics {
|
|
16
|
+
available: boolean;
|
|
17
|
+
openConns: number;
|
|
18
|
+
inUse: number;
|
|
19
|
+
idle: number;
|
|
20
|
+
waitCount: number;
|
|
21
|
+
waitDuration: string;
|
|
22
|
+
maxOpenConns: number;
|
|
23
|
+
}
|
|
24
|
+
|
|
8
25
|
interface AnalyticsState {
|
|
9
26
|
dashboard: any;
|
|
10
27
|
leads: any;
|
|
11
28
|
epinet: any;
|
|
12
29
|
userCounts: any[];
|
|
13
30
|
hourlyNodeActivity: any;
|
|
31
|
+
pulse: PulseMetrics | null;
|
|
32
|
+
system: SystemMetrics | null;
|
|
14
33
|
isLoading: boolean;
|
|
15
34
|
status: string;
|
|
16
35
|
error: string | null;
|
|
@@ -127,7 +146,6 @@ class AnalyticsService {
|
|
|
127
146
|
if (filters.selectedUserId)
|
|
128
147
|
params.append('userId', filters.selectedUserId);
|
|
129
148
|
|
|
130
|
-
// MODIFICATION: Properly format appliedFilters for the backend
|
|
131
149
|
if (filters.appliedFilters && filters.appliedFilters.length > 0) {
|
|
132
150
|
params.append('appliedFilters', JSON.stringify(filters.appliedFilters));
|
|
133
151
|
}
|
|
@@ -145,6 +163,8 @@ class AnalyticsService {
|
|
|
145
163
|
epinet: null,
|
|
146
164
|
userCounts: [],
|
|
147
165
|
hourlyNodeActivity: {},
|
|
166
|
+
pulse: null,
|
|
167
|
+
system: null,
|
|
148
168
|
isLoading: true,
|
|
149
169
|
status: 'loading',
|
|
150
170
|
error: null,
|
|
@@ -153,13 +173,31 @@ class AnalyticsService {
|
|
|
153
173
|
const api = new TractStackAPI(
|
|
154
174
|
window.TRACTSTACK_CONFIG?.tenantId || 'default'
|
|
155
175
|
);
|
|
176
|
+
|
|
156
177
|
const endpoint = `/api/v1/analytics/all${params.toString() ? `?${params.toString()}` : ''}`;
|
|
157
|
-
if (VERBOSE) console.log('🔥 Making API request', { endpoint });
|
|
158
178
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
179
|
+
if (VERBOSE) {
|
|
180
|
+
console.log('🔥 Making API requests', {
|
|
181
|
+
analytics: endpoint,
|
|
182
|
+
pulse: '/api/v1/admin/pulse',
|
|
183
|
+
system: '/api/v1/admin/db/stats',
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
const [analyticsResponse, pulseResponse, systemResponse] =
|
|
188
|
+
await Promise.all([
|
|
189
|
+
api.get(endpoint),
|
|
190
|
+
api.get('/api/v1/admin/pulse'),
|
|
191
|
+
api.get('/api/v1/admin/db/stats'),
|
|
192
|
+
]);
|
|
193
|
+
|
|
194
|
+
if (!analyticsResponse.success) {
|
|
195
|
+
throw new Error(
|
|
196
|
+
analyticsResponse.error || 'Failed to fetch analytics data'
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const data = analyticsResponse.data;
|
|
163
201
|
|
|
164
202
|
const isStillLoading =
|
|
165
203
|
data?.status === 'loading' ||
|
|
@@ -173,12 +211,15 @@ class AnalyticsService {
|
|
|
173
211
|
|
|
174
212
|
if (isStillLoading) {
|
|
175
213
|
if (VERBOSE) console.log('⏳ Backend data still loading, will poll...');
|
|
214
|
+
|
|
176
215
|
const partialAnalytics = {
|
|
177
216
|
dashboard: data.dashboard,
|
|
178
217
|
leads: data.leads,
|
|
179
218
|
epinet: data.epinet,
|
|
180
219
|
userCounts: data.userCounts || [],
|
|
181
220
|
hourlyNodeActivity: data.hourlyNodeActivity || {},
|
|
221
|
+
pulse: pulseResponse.success ? pulseResponse.data?.pulse : null,
|
|
222
|
+
system: systemResponse.success ? systemResponse.data : null,
|
|
182
223
|
status: 'loading',
|
|
183
224
|
error: null,
|
|
184
225
|
isLoading: true,
|
|
@@ -203,6 +244,8 @@ class AnalyticsService {
|
|
|
203
244
|
epinet: data.epinet,
|
|
204
245
|
userCounts: data.userCounts || [],
|
|
205
246
|
hourlyNodeActivity: data.hourlyNodeActivity || {},
|
|
247
|
+
pulse: pulseResponse.success ? pulseResponse.data?.pulse : null,
|
|
248
|
+
system: systemResponse.success ? systemResponse.data : null,
|
|
206
249
|
status: 'complete',
|
|
207
250
|
error: null,
|
|
208
251
|
isLoading: false,
|
|
@@ -211,13 +254,12 @@ class AnalyticsService {
|
|
|
211
254
|
this.setCachedResponse(cacheKey, analyticsData);
|
|
212
255
|
onUpdate(analyticsData);
|
|
213
256
|
|
|
214
|
-
// MODIFICATION: Correctly extract top-level availableFilters and update the store
|
|
215
257
|
epinetCustomFilters.set(window.TRACTSTACK_CONFIG?.tenantId || 'default', {
|
|
216
258
|
...filters,
|
|
217
259
|
availableFilters: data.availableFilters || [],
|
|
218
260
|
});
|
|
219
261
|
|
|
220
|
-
if (VERBOSE) console.log('✅ Analytics
|
|
262
|
+
if (VERBOSE) console.log('✅ Analytics requests completed successfully');
|
|
221
263
|
this.activeRequest = null;
|
|
222
264
|
} catch (error) {
|
|
223
265
|
if (error instanceof Error && error.name === 'AbortError') {
|
|
@@ -231,6 +273,8 @@ class AnalyticsService {
|
|
|
231
273
|
epinet: null,
|
|
232
274
|
userCounts: [],
|
|
233
275
|
hourlyNodeActivity: {},
|
|
276
|
+
pulse: null,
|
|
277
|
+
system: null,
|
|
234
278
|
status: 'error',
|
|
235
279
|
error:
|
|
236
280
|
error instanceof Error ? error.message : 'Unknown error occurred',
|
|
@@ -1,28 +1,17 @@
|
|
|
1
1
|
import type { APIRoute } from '@/types/astro';
|
|
2
2
|
|
|
3
|
-
export const POST: APIRoute = async ({ cookies }) => {
|
|
3
|
+
export const POST: APIRoute = async ({ cookies, url }) => {
|
|
4
4
|
try {
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
let rootDomain: string | undefined;
|
|
5
|
+
const isLocalhost =
|
|
6
|
+
url.hostname === 'localhost' || url.hostname === '127.0.0.1';
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
} catch (e) {
|
|
16
|
-
console.warn('Logout: Failed to parse backend URL for cookie domain', e);
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
// Determine the options ONCE to prevent overwriting
|
|
20
|
-
const cookieOptions: any = { path: '/' };
|
|
21
|
-
if (rootDomain) {
|
|
22
|
-
cookieOptions.domain = rootDomain;
|
|
23
|
-
}
|
|
8
|
+
const cookieOptions: any = {
|
|
9
|
+
path: '/',
|
|
10
|
+
secure: !isLocalhost,
|
|
11
|
+
httpOnly: true,
|
|
12
|
+
sameSite: 'lax',
|
|
13
|
+
};
|
|
24
14
|
|
|
25
|
-
// Execute deletion with the single, correct configuration
|
|
26
15
|
cookies.delete('admin_auth', cookieOptions);
|
|
27
16
|
cookies.delete('editor_auth', cookieOptions);
|
|
28
17
|
|