Admin Panel
Author: Carmine Antonio Bonavoglia Creation Date: 29/10/2025
Last Reviewer: Carmine Antonio Bonavoglia Last Updated: 31/10/2025
Overview
The Admin Panel provides administrative tools for managing clients, licenses, and system operations. It includes:
- Client List Display with expandable license details
- Database Testing utilities
- Session Aggregation tools
- User Metrics Deletion functionality
- Variants Downloader for bulk export operations
Key characteristics:
- Hierarchical client view (clients with expandable licence details)
- Non-blocking data fetching (UI remains responsive during loads)
- Dual data loading (Client + ARShades_Subscription collections)
- Small table variant (compact display format)
- Super Admin-only access (restricted to "Admin" server role)
Location:
- File:
/src/pages/Admin.js - Components:
/src/components/Admin/
Architecture Overview
Admin Panel
├─ Page Entry
│ └─ AdminView (/src/pages/Admin.js)
│
├─ Data Loading
│ ├─ Firestore: Client collection
│ ├─ Firestore: ARShades_Subscription collection
│ └─ License list resolution
│
├─ UI Sections
│ ├─ Admin Configuration Tools
│ │ ├─ DeleteUserMetrics
│ │ ├─ SessionAggregator
│ │ └─ VariantsDownloader
│ │
│ ├─ Client List Table
│ │ ├─ Basic columns (ID, Company Name)
│ │ └─ Expandable rows (Client details + Licenses)
│ │
│ └─ Database Test
│ └─ DbTestComponent
│
└─ State Management
├─ loading: boolean
└─ clients: array (with resolved licenses)
Data Loading Flow
Step 1: Initial Component Mount
File: AdminView.js
useEffect(() => {
const fetchClients = async () => {
setLoading(true);
try {
// 1. Fetch all clients from Client collection
const clientSnap = await getDocs(collection(db, "Client"));
const clientsList = clientSnap.docs.map((docSnap) => ({
id: docSnap.id,
...docSnap.data(),
}));
// 2. Resolve licenses for each client (parallel)
const clientsWithLicenses = await Promise.all(
clientsList.map(async (client) => {
if (
client.licenseList &&
Array.isArray(client.licenseList)
) {
// 3. Fetch each license document
const licensePromises = client.licenseList.map(
async (licId) => {
const licRef = doc(
db,
"ARShades_Subscription",
licId
);
const licSnap = await getDoc(licRef);
return { id: licId, ...licSnap.data() };
}
);
client.licenseDataList = await Promise.all(
licensePromises
);
} else {
client.licenseDataList = [];
}
return client;
})
);
setClients(clientsWithLicenses);
} catch (err) {
console.error("Error fetching clients:", err);
} finally {
setLoading(false);
}
};
fetchClients();
}, []);
Flow Explanation:
- Fetch Clients → Get all documents from
Clientcollection - Map to List → Convert Firestore docs to array with
idfield - Parallel License Resolution → For each client:
- Check if
licenseListarray exists - Map each license ID to a Firestore fetch
- Resolve all promises in parallel with
Promise.all() - Store resolved licenses in
licenseDataList
- Check if
- Set State → Update component state with complete data
- Error Handling → Log errors but don't fail the page load
- Loading State → Set to false when done
Data Structures
Client Document
{
id: string, // Document ID (Client ID)
companyName: string, // Company name for display
licenseList: string[], // Array of license IDs (Firestore refs)
// ... other fields
}
ARShades_Subscription Document
{
id: string, // Document ID (Subscription ID)
subscriptionType: string, // e.g., "Pro", "Enterprise", "Starter"
// ... other subscription details
}
Component State
{
loading: boolean, // Loading indicator
clients: [{
id: string,
companyName: string,
licenseList: string[],
licenseDataList: [{ // Resolved license documents
id: string,
subscriptionType: string
}]
}]
}
UI Components & Sections
Section 1: Admin Configuration Title
<Title level={5} style={{ marginBottom: "20px" }}>
ADMIN CONFIGURATION
</Title>
Display: Single header for admin tools section
Section 2: Admin Configuration Tools
<div style={{ padding: 10 }}>
<DeleteUserMetrics />
<SessionAggregator />
<VariantsDownloader />
</div>
Components:
| Component | Purpose |
|---|---|
DeleteUserMetrics | Remove/clean user activity metrics (undocumented) |
SessionAggregator | Aggregate session data across users (undocumented) |
VariantsDownloader | Download product variants as bulk export (undocumented) |
Note: These components are imported but not fully documented in the current code excerpt.
Section 3: Client List Table
File: AdminView.js
<Table
dataSource={clients}
rowKey="id"
pagination={false}
variant="bordered"
size="small"
columns={[
{ title: "Client ID", dataIndex: "id", key: "id" },
{ title: "Company Name", dataIndex: "companyName", key: "companyName" },
]}
expandable={{
expandedRowRender: (client) => (
<Row gutter={16}>
<Col span={12}>
<h5 style={{ fontStyle: "italic" }}>Client Details</h5>
<p>
Client ID: <strong>{client.id}</strong>
</p>
<p>
Company Name: <strong>{client.companyName}</strong>
</p>
</Col>
<Col span={12}>
<h5 style={{ fontStyle: "italic" }}>License List</h5>
{client.licenseDataList &&
client.licenseDataList.length > 0 ? (
<ul>
{client.licenseDataList.map((license) => (
<li key={license.id}>
<div
style={{
display: "flex",
justifyContent: "space-between",
alignItems: "center",
}}
>
<p>
License:{" "}
{license.subscriptionType || "N/A"}{" "}
- <strong>({license.id})</strong>
</p>
</div>
</li>
))}
</ul>
) : (
<p>No licenses found for this client.</p>
)}
</Col>
</Row>
),
}}
/>
Table Configuration:
| Property | Value | Purpose |
|---|---|---|
dataSource | clients | Populated from state |
rowKey | "id" | Client ID as unique key |
pagination | false | Display all rows (no paging) |
variant | "bordered" | Border styling |
size | "small" | Compact row height |
Columns:
- Client ID — Client's unique identifier
- Company Name — Company display name
Expandable Rows:
When user clicks expand icon on a row:
-
Left Column (Col span=12):
- Client Details header
- Client ID (repeated)
- Company Name (repeated)
-
Right Column (Col span=12):
- License List header
- If licenses exist:
- Unordered list of licenses
- Each license shows: type + ID
- Flex layout with space-between alignment
- If no licenses:
- "No licenses found for this client." message
Section 4: Database Test Component
<div>
<Title level={5} style={{ marginBottom: "20px", marginTop: "40px" }}>
DATABASE TEST
</Title>
</div>
<div>
<DbTestComponent />
</div>
Component: DbTestComponent — Database connection/integrity testing (undocumented)
Rendering Flow
const AdminView = () => {
// 1. Initialize state
const [loading, setLoading] = useState(false);
const [clients, setClients] = useState([]);
// 2. Fetch data on mount
useEffect(() => { ... }, []);
// 3. Render sections
return (
<>
{/* Header */}
<Title>ADMIN CONFIGURATION</Title>
{/* Admin tools */}
<DeleteUserMetrics />
<SessionAggregator />
<VariantsDownloader />
{/* Client list */}
<Title>CLIENT LIST</Title>
{loading ? <Spin /> : <Table ... />}
{/* Database test */}
<Title>DATABASE TEST</Title>
<DbTestComponent />
</>
);
};
Rendering Order:
- Admin Configuration header
- Three admin tool components (DeleteUserMetrics, SessionAggregator, VariantsDownloader)
- Client List header
- Loading spinner OR Table with clients
- Database Test header
- DbTestComponent
Error Handling
Data Fetching Error
catch (err) {
console.error("Error fetching clients:", err);
// Error logged but page continues with empty state
}
finally {
setLoading(false); // Always stop loading
}
Behavior:
- Error logged to console
- Page doesn't crash
- Table displays empty if fetch fails
- User can retry by refreshing page
License Fetch Failure (Non-Blocking)
const licensePromises = client.licenseList.map(async (licId) => {
const licRef = doc(db, "ARShades_Subscription", licId);
const licSnap = await getDoc(licRef);
return { id: licId, ...licSnap.data() }; // Returns empty if not found
});
Behavior:
- If individual license not found: returns
{ id, data: undefined } - UI displays "N/A" for missing subscriptionType
- Client continues loading (doesn't block on one failed license)
Empty License List
{
client.licenseDataList && client.licenseDataList.length > 0 ? (
<ul>...</ul>
) : (
<p>No licenses found for this client.</p>
);
}
Behavior:
- Graceful handling of clients with no licenses
- User-friendly message displayed
- No console errors
Firestore Collections Used
| Collection | Purpose |
|---|---|
Client | Client/company master data |
ARShades_Subscription | Subscription/license information |
Query Pattern:
// 1. Get all clients
getDocs(collection(db, "Client"));
// 2. For each client, fetch referenced licenses
getDoc(doc(db, "ARShades_Subscription", licenseId));
Performance Considerations
Advantages
- Parallel License Resolution — Uses
Promise.all()for concurrent fetches - No Pagination Blocking —
pagination={false}loads all data at once (OK for admin view) - Non-blocking Errors — Individual license failures don't stop client loading
Potential Issues
- No Caching — Fetches all clients & licenses on every mount
- No Lazy Loading — All licenses fetched immediately, even if not viewed
- Large Dataset — With many clients/licenses, could cause performance degradation
- No Search/Filter — Admin must scroll through all clients
Optimization Opportunities
// Could add:
// 1. Pagination
pagination={{ pageSize: 20, showTotal: true }}
// 2. Search/filtering
const [searchClient, setSearchClient] = useState('');
const filteredClients = clients.filter(c =>
c.companyName.includes(searchClient)
);
// 3. Caching with React Query
const { data: clients, isLoading } = useQuery(['clients'], fetchClients, {
staleTime: 5 * 60 * 1000 // 5 minutes
});
// 4. Lazy license loading
// Only fetch licenses when row is expanded
expandable={{
onExpand: (expanded, record) => {
if (expanded && !record.licenseDataList) {
// Load licenses here
}
}
}}
Ant Design Components Used
| Component | Props | Purpose |
|---|---|---|
Table | dataSource, columns, expandable | Main client list display |
Spin | None | Loading indicator |
Row | gutter={16} | 16px spacing between columns |
Col | span={12} | 50% width (half row) |
Typography.Title | level={5} | Section header (h5 size) |
Related Files & Architecture
Pages:
/src/pages/Admin.js(main admin page)
Components:
/src/components/Admin/DbTest.js(database testing)/src/components/Admin/SessionAggregator.js(session data aggregation)/src/components/Admin/DeleteUserMetrics.js(user metrics cleanup)/src/components/Admin/VariantsDownloader.js(bulk variants export)
Services:
/src/data/base.js(Firebase Firestore instance:db)
Firestore Collections:
Client(client/company documents)ARShades_Subscription(subscription/license documents)
Usage Flow
Typical Admin Workflow:
- Access Admin Page → Navigate to admin panel (route-protected)
- View Clients → Table loads and displays all clients with company names
- Expand Client → Click expand icon to see client details and licenses
- Review Licenses → View subscription types and license IDs for each client
- Run Admin Tools → Use DeleteUserMetrics, SessionAggregator, VariantsDownloader as needed
- Test Database → Run DbTestComponent for diagnostics
Security Considerations
Access Control:
- Super Admin only access — This page is restricted to users with the "Admin" server role (Super Admin)
- Route-level protection (assumes authentication check at routing layer)
- No visible auth checks in this component (relies on route guards)
- Direct Firestore access (no backend validation layer)
Role Restrictions:
- Super Admin (server role: "Admin"): ✅ Full access
- Admin (Cliente + mainProfile): ❌ No access
- Member (Cliente + !mainProfile): ❌ No access
- Other roles: ❌ No access
Data Exposure:
- Client IDs and company names visible to Super Admin users
- License IDs and subscription details visible (non-sensitive)
- No personal user data exposed
- Global view across all clients and organizations