EverydayTech Platform - Developer Reference
Complete Source Code Documentation - All Applications
Loading...
Searching...
No Matches
DomainStats.jsx
Go to the documentation of this file.
1import { useState, useEffect } from 'react';
2import { apiFetch } from '../../../lib/api';
3
4function parseJwt(token) {
5 try {
6 const payload = token.split('.')[1];
7 return JSON.parse(atob(payload.replace(/-/g, '+').replace(/_/g, '/')));
8 } catch {
9 return null;
10 }
11}
12
13export default function DomainStats({ domains }) {
14 const [pendingRequests, setPendingRequests] = useState(0);
15 const token = localStorage.getItem('token');
16 const jwtPayload = token ? parseJwt(token) : null;
17 const isRootTenant = jwtPayload?.is_msp || false;
18
19 const activeDomains = domains.filter(d => d.status === 'active').length;
20
21 // Count registered vs DNS-only domains
22 const registeredDomains = domains.filter(d => d.metadata?.domain_type === 'registered').length;
23 const dnsOnlyDomains = domains.filter(d => d.metadata?.domain_type === 'dns_only').length;
24
25 // Count domains with Cloudflare enrichment
26 const cloudflareEnrichedDomains = domains.filter(d =>
27 d.metadata?.has_cloudflare_dns || d.metadata?.cloudflare_zone_id
28 ).length;
29
30 // Count locked domains
31 const lockedDomains = domains.filter(d => d.metadata?.locked === true).length;
32
33 const expiringSoon = domains.filter(d => {
34 if (!d.expiration_date || d.metadata?.domain_type === 'dns_only') return false;
35 const days = Math.ceil((new Date(d.expiration_date) - new Date()) / (1000 * 60 * 60 * 24));
36 return days <= 30 && days > 0;
37 }).length;
38
39 // Find most recent update time
40 const lastSync = domains.length > 0
41 ? domains.reduce((latest, d) => {
42 const updated = new Date(d.updated_at);
43 return updated > latest ? updated : latest;
44 }, new Date(0))
45 : null;
46
47 // Load pending requests count for root tenant
48 useEffect(() => {
49 if (isRootTenant) {
50 loadPendingCount();
51 }
52 }, [isRootTenant]);
53
54 const loadPendingCount = async () => {
55 try {
56 const res = await apiFetch('/domain-requests?status=pending');
57 if (!res.ok) return;
58 const data = await res.json();
59 const count = data.total || (Array.isArray(data) ? data.length : data.requests?.length || 0);
60 setPendingRequests(count);
61 } catch (err) {
62 console.error('Error loading pending requests:', err);
63 }
64 };
65
66 return (
67 <div className="tab-stats">
68 <div className="stat-card">
69 <span className="stat-label">Total Domains</span>
70 <span className="stat-value">{domains.length}</span>
71 {lastSync && (
72 <small style={{ fontSize: '0.75em', color: '#666', marginTop: '4px', display: 'block' }}>
73 Last synced: {lastSync.toLocaleTimeString()}
74 </small>
75 )}
76 </div>
77 <div className="stat-card">
78 <span className="stat-label">Registered</span>
79 <span className="stat-value">{registeredDomains}</span>
80 <small style={{ fontSize: '0.75em', color: '#666', marginTop: '4px', display: 'block' }}>
81 Full management
82 </small>
83 </div>
84 <div className="stat-card">
85 <span className="stat-label">DNS Only</span>
86 <span className="stat-value">{dnsOnlyDomains}</span>
87 <small style={{ fontSize: '0.75em', color: '#666', marginTop: '4px', display: 'block' }}>
88 DNS management only
89 </small>
90 </div>
91 <div className="stat-card" style={{ background: cloudflareEnrichedDomains > 0 ? '#e3f2fd' : undefined }}>
92 <span className="stat-label">☁️ Cloudflare DNS</span>
93 <span className="stat-value" style={{ color: cloudflareEnrichedDomains > 0 ? '#1976d2' : undefined }}>
94 {cloudflareEnrichedDomains}
95 </span>
96 <small style={{ fontSize: '0.75em', color: '#666', marginTop: '4px', display: 'block' }}>
97 Using Cloudflare
98 </small>
99 </div>
100 <div className="stat-card" style={{ background: lockedDomains > 0 ? '#e8f5e9' : undefined }}>
101 <span className="stat-label">🔒 Locked</span>
102 <span className="stat-value" style={{ color: lockedDomains > 0 ? '#388e3c' : undefined }}>
103 {lockedDomains}
104 </span>
105 <small style={{ fontSize: '0.75em', color: '#666', marginTop: '4px', display: 'block' }}>
106 Transfer protected
107 </small>
108 </div>
109 <div className="stat-card" style={{ background: expiringSoon > 0 ? '#fff3e0' : undefined }}>
110 <span className="stat-label">⚠️ Expiring Soon</span>
111 <span className="stat-value" style={{ color: expiringSoon > 0 ? '#e65100' : undefined }}>
112 {expiringSoon}
113 </span>
114 <small style={{ fontSize: '0.75em', color: '#666', marginTop: '4px', display: 'block' }}>
115 Within 30 days
116 </small>
117 </div>
118 {isRootTenant && (
119 <div className="stat-card" style={{ background: pendingRequests > 0 ? '#fff3e0' : undefined }}>
120 <span className="stat-label">Pending Requests</span>
121 <span className="stat-value" style={{ color: pendingRequests > 0 ? '#e65100' : undefined }}>
122 {pendingRequests}
123 </span>
124 {pendingRequests > 0 && (
125 <small style={{ fontSize: '0.75em', color: '#e65100', marginTop: '4px', display: 'block' }}>
126 Requires approval
127 </small>
128 )}
129 </div>
130 )}
131 </div>
132 );
133}