Skip to main content

Backend Dashboard

The Backend Dashboard is the administrative interface for the Heimdall platform, built with Next.js 15 and the App Router.

Features

Dashboard

The main dashboard provides an overview of platform statistics and recent activity:

  • Active connections count
  • GPS data points
  • User statistics
  • Recent activity feed

GPS Management

View and manage GPS tracking data:

  • Real-time GPS position display
  • Historical GPS data table with pagination
  • GPS data export functionality
  • Interactive map visualization

User Management

Manage platform users (Admin only):

  • View all registered users
  • Assign roles and permissions
  • View user activity
  • Suspend/ban users

Developer Tools

Tools for developers integrating with Heimdall:

  • OAuth application management
  • Create/edit OAuth apps
  • View client credentials
  • Manage redirect URIs

Pages & Routes

RouteDescriptionAuth Required
/Landing pageNo
/auth/loginLogin pageNo
/auth/errorAuth error pageNo
/dashboardMain dashboardYes
/developer/oauth-appsOAuth app listYes
/developer/oauth-apps/newCreate OAuth appYes
/developer/oauth-apps/[id]Edit OAuth appYes

Authentication

The Backend Dashboard uses NextAuth with a custom Heimdall provider:

// src/lib/auth/heimdall-provider.ts
import { OAuthConfig } from "next-auth/providers";

export const HeimdallProvider: OAuthConfig<Profile> = {
id: "heimdall",
name: "Heimdall",
type: "oauth",
authorization: {
url: `${process.env.HEIMDALL_ID_URL}/oauth/authorize`,
params: { scope: "openid profile email" }
},
token: `${process.env.API_URL}/v1/oauth/token`,
userinfo: `${process.env.API_URL}/v1/oauth/userinfo`,
profile(profile) {
return {
id: profile.sub,
name: profile.name,
email: profile.email,
image: profile.picture
};
}
};

Session Handling

Sessions are managed via NextAuth with JWT strategy:

// middleware.ts
import { auth } from "@/lib/auth";

export default auth((req) => {
if (!req.auth && req.nextUrl.pathname.startsWith("/dashboard")) {
const newUrl = new URL("/auth/login", req.nextUrl.origin);
newUrl.searchParams.set("callbackUrl", req.nextUrl.pathname);
return Response.redirect(newUrl);
}
});

export const config = {
matcher: ["/dashboard/:path*", "/developer/:path*"]
};

Components

Protected Route

Wrap pages that require authentication:

import { ProtectedRoute } from "@/components/auth/ProtectedRoute";

export default function DashboardPage() {
return (
<ProtectedRoute>
<Dashboard />
</ProtectedRoute>
);
}

Protected Content

Conditionally render content based on authentication:

import { ProtectedContent } from "@/components/auth/ProtectedContent";

export default function Page() {
return (
<ProtectedContent
fallback={<LoginPrompt />}
requiredPermissions={["gps:read"]}
>
<GpsDataTable />
</ProtectedContent>
);
}

User Button

Display user info and logout:

import { UserButton } from "@/components/auth/UserButton";

export default function Header() {
return (
<nav>
<UserButton />
</nav>
);
}

Hooks

useAuth

Access authentication state:

import { useAuth } from "@/hooks/useAuth";

function Component() {
const { user, isLoading, isAuthenticated } = useAuth();

if (isLoading) return <Spinner />;
if (!isAuthenticated) return <LoginPrompt />;

return <div>Welcome, {user.name}</div>;
}

usePermissions

Check user permissions:

import { usePermissions } from "@/hooks/usePermissions";

function Component() {
const { permissions, hasPermission, isLoading } = usePermissions();

if (hasPermission("gps:write")) {
return <CreateGpsButton />;
}

return null;
}

API Integration

GraphQL Client

The dashboard uses Apollo Client for GraphQL:

// src/components/providers/ApolloWrapper.tsx
"use client";

import { ApolloProvider, ApolloClient, InMemoryCache } from "@apollo/client";
import { useSession } from "next-auth/react";

export function ApolloWrapper({ children }) {
const { data: session } = useSession();

const client = new ApolloClient({
uri: `${process.env.NEXT_PUBLIC_API_URL}/v1/gql`,
cache: new InMemoryCache(),
headers: {
Authorization: session?.accessToken
? `Bearer ${session.accessToken}`
: ""
}
});

return <ApolloProvider client={client}>{children}</ApolloProvider>;
}

Example Query

import { gql, useQuery } from "@apollo/client";

const GET_GPS_DATA = gql`
query GetGpsData($page: Int!, $limit: Int!) {
gpsList(page: $page, limit: $limit) {
data {
id
latitude
longitude
altitude
timestamp
speed
}
pagination {
page
limit
total
totalPages
}
}
}
`;

function GpsDataTable() {
const { data, loading, error } = useQuery(GET_GPS_DATA, {
variables: { page: 1, limit: 10 }
});

if (loading) return <Spinner />;
if (error) return <Error message={error.message} />;

return (
<table>
{data.gpsList.data.map((gps) => (
<tr key={gps.id}>
<td>{gps.latitude}</td>
<td>{gps.longitude}</td>
<td>{gps.speed}</td>
</tr>
))}
</table>
);
}

Layouts

Dashboard Layout

The main layout for authenticated pages:

// src/app/dashboard/layout.tsx
import { DashboardLayout } from "@/components/layouts/DashboardLayout";

export default function Layout({ children }) {
return <DashboardLayout>{children}</DashboardLayout>;
}

Features:

  • Sidebar navigation
  • Header with user menu
  • Breadcrumbs
  • Footer

Overlay Layout

Full-screen overlay layout for modals:

import { OverlayLayout } from "@/layouts/OverlayLayout";

export default function ModalPage() {
return (
<OverlayLayout>
<Modal />
</OverlayLayout>
);
}

UI Components

The dashboard includes a set of reusable UI components:

Button

import { Button } from "@/components/ui/Button";

<Button variant="primary" size="lg" onClick={handleClick}>
Click Me
</Button>

<Button variant="outline" disabled>
Disabled
</Button>

Alert

import { Alert } from "@/components/ui/Alert";

<Alert type="success" title="Success!">
Your changes have been saved.
</Alert>

<Alert type="error" title="Error">
Something went wrong.
</Alert>
import { Modal } from "@/components/ui/Modal";

<Modal isOpen={isOpen} onClose={handleClose} title="Confirm Action">
<p>Are you sure you want to proceed?</p>
<div>
<Button onClick={handleClose}>Cancel</Button>
<Button variant="primary" onClick={handleConfirm}>
Confirm
</Button>
</div>
</Modal>

Loading Spinner

import { LoadingSpinner } from "@/components/ui/LoadingSpinner";

<LoadingSpinner size="lg" />

Floating Input

import { FloatingInput } from "@/components/ui/FloatingInput";

<FloatingInput
label="Email"
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
/>

Environment Variables

VariableDescriptionRequired
NEXTAUTH_URLThe base URL of the appYes
NEXTAUTH_SECRETSecret for signing tokensYes
NEXT_PUBLIC_API_URLPublic API URLYes
API_URLServer-side API URLYes
HEIMDALL_ID_URLHeimdall ID service URLYes

Development

cd platform/backend

# Install dependencies
pnpm install

# Run development server
pnpm dev

# Build for production
pnpm build

# Start production server
pnpm start

# Run linting
pnpm lint

# Run E2E tests
pnpm test:e2e

Next Steps