|
@@ -71,7 +71,7 @@ export default function AnalyticsDashboard({ shortCode, onClose }: AnalyticsDash
|
|
|
if (error) {
|
|
if (error) {
|
|
|
return (
|
|
return (
|
|
|
<div className="p-6 text-center">
|
|
<div className="p-6 text-center">
|
|
|
- <div className="text-red-600 mb-4">{error}</div>
|
|
|
|
|
|
|
+ <div className="text-red-600 dark:text-red-400 mb-4">{error}</div>
|
|
|
<button onClick={fetchAnalytics} className="btn btn-secondary">
|
|
<button onClick={fetchAnalytics} className="btn btn-secondary">
|
|
|
Try Again
|
|
Try Again
|
|
|
</button>
|
|
</button>
|
|
@@ -88,10 +88,16 @@ export default function AnalyticsDashboard({ shortCode, onClose }: AnalyticsDash
|
|
|
{/* Header */}
|
|
{/* Header */}
|
|
|
{onClose && (
|
|
{onClose && (
|
|
|
<div className="flex justify-between items-center">
|
|
<div className="flex justify-between items-center">
|
|
|
- <h2 className="text-xl font-semibold text-gray-900">Image Analytics</h2>
|
|
|
|
|
|
|
+ <h2
|
|
|
|
|
+ className="text-xl font-semibold"
|
|
|
|
|
+ style={{ color: 'var(--text-primary)' }}
|
|
|
|
|
+ >
|
|
|
|
|
+ Image Analytics
|
|
|
|
|
+ </h2>
|
|
|
<button
|
|
<button
|
|
|
onClick={onClose}
|
|
onClick={onClose}
|
|
|
- className="text-gray-400 hover:text-gray-600"
|
|
|
|
|
|
|
+ className="transition-colors"
|
|
|
|
|
+ style={{ color: 'var(--text-muted)' }}
|
|
|
>
|
|
>
|
|
|
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
<svg className="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
|
@@ -102,34 +108,88 @@ export default function AnalyticsDashboard({ shortCode, onClose }: AnalyticsDash
|
|
|
|
|
|
|
|
{/* Stats Grid */}
|
|
{/* Stats Grid */}
|
|
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
|
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
|
|
|
- <div className="bg-gray-50 rounded-lg p-4">
|
|
|
|
|
- <div className="text-sm text-gray-500 mb-1">Total Views</div>
|
|
|
|
|
- <div className="text-2xl font-bold text-gray-900">{analytics.totalViews}</div>
|
|
|
|
|
|
|
+ <div
|
|
|
|
|
+ className="rounded-lg p-4"
|
|
|
|
|
+ style={{ backgroundColor: 'var(--bg-tertiary)' }}
|
|
|
|
|
+ >
|
|
|
|
|
+ <div className="text-sm mb-1" style={{ color: 'var(--text-muted)' }}>
|
|
|
|
|
+ Total Views
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div
|
|
|
|
|
+ className="text-2xl font-bold"
|
|
|
|
|
+ style={{ color: 'var(--text-primary)' }}
|
|
|
|
|
+ >
|
|
|
|
|
+ {analytics.totalViews}
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
- <div className="bg-gray-50 rounded-lg p-4">
|
|
|
|
|
- <div className="text-sm text-gray-500 mb-1">Unique Visitors</div>
|
|
|
|
|
- <div className="text-2xl font-bold text-gray-900">{analytics.uniqueVisitors}</div>
|
|
|
|
|
|
|
+ <div
|
|
|
|
|
+ className="rounded-lg p-4"
|
|
|
|
|
+ style={{ backgroundColor: 'var(--bg-tertiary)' }}
|
|
|
|
|
+ >
|
|
|
|
|
+ <div className="text-sm mb-1" style={{ color: 'var(--text-muted)' }}>
|
|
|
|
|
+ Unique Visitors
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div
|
|
|
|
|
+ className="text-2xl font-bold"
|
|
|
|
|
+ style={{ color: 'var(--text-primary)' }}
|
|
|
|
|
+ >
|
|
|
|
|
+ {analytics.uniqueVisitors}
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
- <div className="bg-gray-50 rounded-lg p-4">
|
|
|
|
|
- <div className="text-sm text-gray-500 mb-1">Proxy/VPN Views</div>
|
|
|
|
|
- <div className="text-2xl font-bold text-gray-900">{analytics.proxyViewsCount}</div>
|
|
|
|
|
|
|
+ <div
|
|
|
|
|
+ className="rounded-lg p-4"
|
|
|
|
|
+ style={{ backgroundColor: 'var(--bg-tertiary)' }}
|
|
|
|
|
+ >
|
|
|
|
|
+ <div className="text-sm mb-1" style={{ color: 'var(--text-muted)' }}>
|
|
|
|
|
+ Proxy/VPN Views
|
|
|
|
|
+ </div>
|
|
|
|
|
+ <div
|
|
|
|
|
+ className="text-2xl font-bold"
|
|
|
|
|
+ style={{ color: 'var(--text-primary)' }}
|
|
|
|
|
+ >
|
|
|
|
|
+ {analytics.proxyViewsCount}
|
|
|
|
|
+ </div>
|
|
|
</div>
|
|
</div>
|
|
|
</div>
|
|
</div>
|
|
|
|
|
|
|
|
{/* Referrers */}
|
|
{/* Referrers */}
|
|
|
{Object.keys(analytics.viewsByReferrer).length > 0 && (
|
|
{Object.keys(analytics.viewsByReferrer).length > 0 && (
|
|
|
<div>
|
|
<div>
|
|
|
- <h3 className="text-sm font-medium text-gray-700 mb-3">Top Referrers</h3>
|
|
|
|
|
- <div className="bg-gray-50 rounded-lg divide-y divide-gray-200">
|
|
|
|
|
|
|
+ <h3
|
|
|
|
|
+ className="text-sm font-medium mb-3"
|
|
|
|
|
+ style={{ color: 'var(--text-secondary)' }}
|
|
|
|
|
+ >
|
|
|
|
|
+ Top Referrers
|
|
|
|
|
+ </h3>
|
|
|
|
|
+ <div
|
|
|
|
|
+ className="rounded-lg divide-y"
|
|
|
|
|
+ style={{
|
|
|
|
|
+ backgroundColor: 'var(--bg-tertiary)',
|
|
|
|
|
+ borderColor: 'var(--border)',
|
|
|
|
|
+ }}
|
|
|
|
|
+ >
|
|
|
{Object.entries(analytics.viewsByReferrer)
|
|
{Object.entries(analytics.viewsByReferrer)
|
|
|
.sort(([, a], [, b]) => b - a)
|
|
.sort(([, a], [, b]) => b - a)
|
|
|
.slice(0, 5)
|
|
.slice(0, 5)
|
|
|
.map(([referrer, count]) => (
|
|
.map(([referrer, count]) => (
|
|
|
- <div key={referrer} className="px-4 py-3 flex justify-between items-center">
|
|
|
|
|
- <span className="text-sm text-gray-600 truncate max-w-xs" title={referrer}>
|
|
|
|
|
|
|
+ <div
|
|
|
|
|
+ key={referrer}
|
|
|
|
|
+ className="px-4 py-3 flex justify-between items-center"
|
|
|
|
|
+ style={{ borderColor: 'var(--border)' }}
|
|
|
|
|
+ >
|
|
|
|
|
+ <span
|
|
|
|
|
+ className="text-sm truncate max-w-xs"
|
|
|
|
|
+ style={{ color: 'var(--text-secondary)' }}
|
|
|
|
|
+ title={referrer}
|
|
|
|
|
+ >
|
|
|
{referrer}
|
|
{referrer}
|
|
|
</span>
|
|
</span>
|
|
|
- <span className="text-sm font-medium text-gray-900">{count}</span>
|
|
|
|
|
|
|
+ <span
|
|
|
|
|
+ className="text-sm font-medium"
|
|
|
|
|
+ style={{ color: 'var(--text-primary)' }}
|
|
|
|
|
+ >
|
|
|
|
|
+ {count}
|
|
|
|
|
+ </span>
|
|
|
</div>
|
|
</div>
|
|
|
))}
|
|
))}
|
|
|
</div>
|
|
</div>
|
|
@@ -138,47 +198,85 @@ export default function AnalyticsDashboard({ shortCode, onClose }: AnalyticsDash
|
|
|
|
|
|
|
|
{/* Recent Views Table */}
|
|
{/* Recent Views Table */}
|
|
|
<div>
|
|
<div>
|
|
|
- <h3 className="text-sm font-medium text-gray-700 mb-3">Recent Views</h3>
|
|
|
|
|
|
|
+ <h3
|
|
|
|
|
+ className="text-sm font-medium mb-3"
|
|
|
|
|
+ style={{ color: 'var(--text-secondary)' }}
|
|
|
|
|
+ >
|
|
|
|
|
+ Recent Views
|
|
|
|
|
+ </h3>
|
|
|
{analytics.recentViews.length === 0 ? (
|
|
{analytics.recentViews.length === 0 ? (
|
|
|
- <p className="text-gray-500 text-sm">No views recorded yet</p>
|
|
|
|
|
|
|
+ <p className="text-sm" style={{ color: 'var(--text-muted)' }}>
|
|
|
|
|
+ No views recorded yet
|
|
|
|
|
+ </p>
|
|
|
) : (
|
|
) : (
|
|
|
<div className="overflow-x-auto">
|
|
<div className="overflow-x-auto">
|
|
|
- <table className="min-w-full divide-y divide-gray-200">
|
|
|
|
|
- <thead className="bg-gray-50">
|
|
|
|
|
|
|
+ <table
|
|
|
|
|
+ className="min-w-full divide-y"
|
|
|
|
|
+ style={{ borderColor: 'var(--border)' }}
|
|
|
|
|
+ >
|
|
|
|
|
+ <thead style={{ backgroundColor: 'var(--bg-tertiary)' }}>
|
|
|
<tr>
|
|
<tr>
|
|
|
- <th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
|
|
|
|
|
+ <th
|
|
|
|
|
+ className="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider"
|
|
|
|
|
+ style={{ color: 'var(--text-muted)' }}
|
|
|
|
|
+ >
|
|
|
Time
|
|
Time
|
|
|
</th>
|
|
</th>
|
|
|
- <th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
|
|
|
|
|
+ <th
|
|
|
|
|
+ className="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider"
|
|
|
|
|
+ style={{ color: 'var(--text-muted)' }}
|
|
|
|
|
+ >
|
|
|
IP Address
|
|
IP Address
|
|
|
</th>
|
|
</th>
|
|
|
- <th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
|
|
|
|
|
+ <th
|
|
|
|
|
+ className="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider"
|
|
|
|
|
+ style={{ color: 'var(--text-muted)' }}
|
|
|
|
|
+ >
|
|
|
Referrer
|
|
Referrer
|
|
|
</th>
|
|
</th>
|
|
|
- <th className="px-4 py-3 text-left text-xs font-medium text-gray-500 uppercase tracking-wider">
|
|
|
|
|
|
|
+ <th
|
|
|
|
|
+ className="px-4 py-3 text-left text-xs font-medium uppercase tracking-wider"
|
|
|
|
|
+ style={{ color: 'var(--text-muted)' }}
|
|
|
|
|
+ >
|
|
|
Proxy
|
|
Proxy
|
|
|
</th>
|
|
</th>
|
|
|
</tr>
|
|
</tr>
|
|
|
</thead>
|
|
</thead>
|
|
|
- <tbody className="bg-white divide-y divide-gray-200">
|
|
|
|
|
|
|
+ <tbody
|
|
|
|
|
+ className="divide-y"
|
|
|
|
|
+ style={{
|
|
|
|
|
+ backgroundColor: 'var(--bg-secondary)',
|
|
|
|
|
+ borderColor: 'var(--border)',
|
|
|
|
|
+ }}
|
|
|
|
|
+ >
|
|
|
{analytics.recentViews.slice(0, 20).map((view: ViewLog) => (
|
|
{analytics.recentViews.slice(0, 20).map((view: ViewLog) => (
|
|
|
<tr key={view.id}>
|
|
<tr key={view.id}>
|
|
|
- <td className="px-4 py-3 whitespace-nowrap text-sm text-gray-600">
|
|
|
|
|
|
|
+ <td
|
|
|
|
|
+ className="px-4 py-3 whitespace-nowrap text-sm"
|
|
|
|
|
+ style={{ color: 'var(--text-secondary)' }}
|
|
|
|
|
+ >
|
|
|
{formatDate(view.viewedAt)}
|
|
{formatDate(view.viewedAt)}
|
|
|
</td>
|
|
</td>
|
|
|
- <td className="px-4 py-3 whitespace-nowrap text-sm text-gray-600">
|
|
|
|
|
|
|
+ <td
|
|
|
|
|
+ className="px-4 py-3 whitespace-nowrap text-sm"
|
|
|
|
|
+ style={{ color: 'var(--text-secondary)' }}
|
|
|
|
|
+ >
|
|
|
{view.viewerIp}
|
|
{view.viewerIp}
|
|
|
</td>
|
|
</td>
|
|
|
- <td className="px-4 py-3 text-sm text-gray-600 max-w-xs truncate" title={view.referrer || undefined}>
|
|
|
|
|
|
|
+ <td
|
|
|
|
|
+ className="px-4 py-3 text-sm max-w-xs truncate"
|
|
|
|
|
+ style={{ color: 'var(--text-secondary)' }}
|
|
|
|
|
+ title={view.referrer || undefined}
|
|
|
|
|
+ >
|
|
|
{view.referrer || '-'}
|
|
{view.referrer || '-'}
|
|
|
</td>
|
|
</td>
|
|
|
<td className="px-4 py-3 whitespace-nowrap text-sm">
|
|
<td className="px-4 py-3 whitespace-nowrap text-sm">
|
|
|
{view.isProxyDetected ? (
|
|
{view.isProxyDetected ? (
|
|
|
- <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-yellow-100 text-yellow-800">
|
|
|
|
|
|
|
+ <span className="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400">
|
|
|
Detected
|
|
Detected
|
|
|
</span>
|
|
</span>
|
|
|
) : (
|
|
) : (
|
|
|
- <span className="text-gray-400">-</span>
|
|
|
|
|
|
|
+ <span style={{ color: 'var(--text-muted)' }}>-</span>
|
|
|
)}
|
|
)}
|
|
|
</td>
|
|
</td>
|
|
|
</tr>
|
|
</tr>
|