Skip to main content
Version: 1.0.0

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:

  1. Fetch Clients → Get all documents from Client collection
  2. Map to List → Convert Firestore docs to array with id field
  3. Parallel License Resolution → For each client:
    • Check if licenseList array exists
    • Map each license ID to a Firestore fetch
    • Resolve all promises in parallel with Promise.all()
    • Store resolved licenses in licenseDataList
  4. Set State → Update component state with complete data
  5. Error Handling → Log errors but don't fail the page load
  6. 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:

ComponentPurpose
DeleteUserMetricsRemove/clean user activity metrics (undocumented)
SessionAggregatorAggregate session data across users (undocumented)
VariantsDownloaderDownload 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:

PropertyValuePurpose
dataSourceclientsPopulated from state
rowKey"id"Client ID as unique key
paginationfalseDisplay all rows (no paging)
variant"bordered"Border styling
size"small"Compact row height

Columns:

  1. Client ID — Client's unique identifier
  2. Company Name — Company display name

Expandable Rows:

When user clicks expand icon on a row:

  1. Left Column (Col span=12):

    • Client Details header
    • Client ID (repeated)
    • Company Name (repeated)
  2. 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:

  1. Admin Configuration header
  2. Three admin tool components (DeleteUserMetrics, SessionAggregator, VariantsDownloader)
  3. Client List header
  4. Loading spinner OR Table with clients
  5. Database Test header
  6. 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

CollectionPurpose
ClientClient/company master data
ARShades_SubscriptionSubscription/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 Blockingpagination={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

ComponentPropsPurpose
TabledataSource, columns, expandableMain client list display
SpinNoneLoading indicator
Rowgutter={16}16px spacing between columns
Colspan={12}50% width (half row)
Typography.Titlelevel={5}Section header (h5 size)

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:

  1. Access Admin Page → Navigate to admin panel (route-protected)
  2. View Clients → Table loads and displays all clients with company names
  3. Expand Client → Click expand icon to see client details and licenses
  4. Review Licenses → View subscription types and license IDs for each client
  5. Run Admin Tools → Use DeleteUserMetrics, SessionAggregator, VariantsDownloader as needed
  6. 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