UI-PORTAL-TEMPLATE-SPEC — Astro + Preact UI Starter Repository Specification¶
Status: Draft
Owner: Platform Engineering
Repository: simpaisa/ui-portal-template
Framework: Astro 6.x + Preact
Last Updated: 2026-04-03
1. Overview¶
Canonical UI starter repository for Simpaisa merchant and internal portals. Built on Astro 6.x with Preact islands for interactive components. Deployed to Cloudflare Pages. Handles dashboard and management interfaces for the payment gateway serving 270M+ annual transactions across PK, BD, NP, and IQ corridors.
2. Directory Structure¶
ui-portal-template/
├── src/
│ ├── pages/
│ │ ├── index.astro # Landing / dashboard page
│ │ ├── login.astro # Authentication page
│ │ ├── payin/
│ │ │ ├── index.astro # Pay-in transaction list
│ │ │ └── [id].astro # Pay-in transaction detail
│ │ ├── payout/
│ │ │ ├── index.astro # Pay-out list
│ │ │ └── [id].astro # Pay-out detail
│ │ ├── remittance/
│ │ │ ├── index.astro # Remittance list
│ │ │ └── [id].astro # Remittance detail
│ │ ├── cards/
│ │ │ ├── index.astro # Card transactions list
│ │ │ └── [id].astro # Card transaction detail
│ │ └── settings/
│ │ ├── index.astro # Account settings
│ │ ├── api-keys.astro # API key management
│ │ └── webhooks.astro # Webhook configuration
│ ├── components/
│ │ ├── layout/
│ │ │ ├── Header.astro # Navigation header (static)
│ │ │ ├── Sidebar.astro # Side navigation (static)
│ │ │ └── Footer.astro # Page footer (static)
│ │ ├── ui/
│ │ │ ├── Button.astro # Button variants
│ │ │ ├── Card.astro # Content card
│ │ │ ├── Badge.astro # Status badge
│ │ │ ├── Table.astro # Data table (static)
│ │ │ └── Alert.astro # Alert/notification
│ │ └── common/
│ │ ├── StatusBadge.astro # Transaction status with colour coding
│ │ ├── CurrencyDisplay.astro # Formatted currency (corridor-aware)
│ │ └── DateDisplay.astro # Localised date/time display
│ ├── islands/
│ │ ├── TransactionFilter.tsx # Interactive filter/search (Preact)
│ │ ├── TransactionTable.tsx # Sortable, paginated table (Preact)
│ │ ├── DashboardCharts.tsx # Real-time charts (Preact)
│ │ ├── WebhookTester.tsx # Send test webhooks (Preact)
│ │ ├── ApiKeyManager.tsx # Create/revoke API keys (Preact)
│ │ ├── BatchUploader.tsx # CSV batch upload (Preact)
│ │ └── NotificationToast.tsx # Toast notification system (Preact)
│ ├── layouts/
│ │ ├── BaseLayout.astro # HTML shell, head, meta, fonts
│ │ ├── DashboardLayout.astro # Authenticated layout with sidebar
│ │ └── AuthLayout.astro # Unauthenticated layout (login, register)
│ ├── styles/
│ │ ├── global.css # CSS reset, custom properties, base styles
│ │ ├── tokens.css # Design system tokens
│ │ └── utilities.css # Utility classes
│ ├── lib/
│ │ ├── auth.ts # ControlPlane.com auth client
│ │ ├── api.ts # Simpaisa API client (fetch wrapper)
│ │ ├── constants.ts # Environment URLs, corridor config
│ │ └── utils.ts # Formatting, validation helpers
│ └── middleware/
│ └── index.ts # Astro middleware (auth guard, CSRF)
├── public/
│ ├── favicon.svg
│ └── fonts/
├── tests/
│ ├── unit/ # Vitest unit tests
│ └── e2e/ # Playwright E2E tests
├── astro.config.mjs
├── tsconfig.json
├── vitest.config.ts
├── playwright.config.ts
├── package.json
└── wrangler.toml # Cloudflare Pages config
3. Astro 6.x Configuration¶
// astro.config.mjs
import { defineConfig } from 'astro/config';
import preact from '@astrojs/preact';
import cloudflare from '@astrojs/cloudflare';
export default defineConfig({
output: 'server',
adapter: cloudflare(),
integrations: [preact({ compat: true })],
vite: {
build: { target: 'esnext' },
},
});
- Server-side rendering via Cloudflare Pages Functions
- Preact integration with React compatibility mode
- TypeScript strict mode enabled
4. Preact Islands Architecture¶
- Static content rendered at build time or server-side by Astro
- Interactive components in
src/islands/as Preact TSX files - Islands hydrate on the client using Astro's
client:*directives: client:load— hydrate immediately (critical interactive elements)client:visible— hydrate when scrolled into view (charts, tables below fold)client:idle— hydrate when browser is idle (non-critical)- Islands communicate via custom events or shared state (nano stores)
- Keep islands small and focused; prefer Astro components for static UI
5. ControlPlane.com Auth Middleware¶
// src/lib/auth.ts
export interface AuthConfig {
orgId: string;
clientId: string;
redirectUri: string;
audience: string;
}
- OIDC/OAuth 2.0 integration with ControlPlane.com identity provider
- Astro middleware intercepts requests, validates JWT access tokens
- Redirect unauthenticated users to login page
- Token refresh handled automatically (refresh token rotation)
- Role-based access:
merchant:admin,merchant:operator,merchant:viewer - Session stored in encrypted HTTP-only cookie
6. Cloudflare Pages Adapter¶
- SSR via Cloudflare Pages Functions (V8 isolates)
- Environment variables via
wrangler.tomland Cloudflare dashboard - KV binding for session storage
- Preview deployments on every pull request branch
- Production deployment on merge to
main
7. Design System Tokens¶
/* src/styles/tokens.css */
:root {
/* Colour palette */
--colour-primary-50: #f0f4ff;
--colour-primary-500: #3366ff;
--colour-primary-900: #0a1a4d;
--colour-success: #22c55e;
--colour-warning: #f59e0b;
--colour-error: #ef4444;
--colour-neutral-50: #f9fafb;
--colour-neutral-900: #111827;
/* Typography */
--font-sans: 'Inter', system-ui, sans-serif;
--font-mono: 'JetBrains Mono', monospace;
--text-xs: 0.75rem;
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.125rem;
--text-xl: 1.25rem;
--text-2xl: 1.5rem;
/* Spacing */
--space-1: 0.25rem;
--space-2: 0.5rem;
--space-3: 0.75rem;
--space-4: 1rem;
--space-6: 1.5rem;
--space-8: 2rem;
/* Border radius */
--radius-sm: 0.25rem;
--radius-md: 0.5rem;
--radius-lg: 0.75rem;
/* Shadows */
--shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
--shadow-md: 0 4px 6px rgba(0,0,0,0.07);
/* Breakpoints (reference, used in media queries) */
/* --bp-sm: 640px; --bp-md: 768px; --bp-lg: 1024px; --bp-xl: 1280px; */
}
- Dark mode via
[data-theme="dark"]selector on<html> - Corridor-specific branding tokens (optional override per deployment)
- No CSS framework dependency — custom properties only
8. TypeScript Strict Mode¶
{
"compilerOptions": {
"strict": true,
"noUncheckedIndexedAccess": true,
"exactOptionalPropertyTypes": true,
"noImplicitOverride": true
}
}
- All
.tsand.tsxfiles under strict mode - API response types shared with
@simpaisa/sdktype definitions - No
anytypes permitted (enforced by ESLint rule)
9. Testing¶
Unit tests (Vitest):
- Test Preact islands in isolation with @testing-library/preact
- Test utility functions and API client
- Test auth middleware logic
- Coverage threshold: >80%
E2E tests (Playwright):
- Login flow (ControlPlane.com auth)
- Transaction list navigation and filtering
- Transaction detail view
- Webhook configuration flow
- Responsive layout verification (mobile, tablet, desktop)
- Accessibility audit via @axe-core/playwright
10. Responsive Layout (Mobile-First)¶
- Base styles target mobile (320px+)
- Breakpoints: 640px (sm), 768px (md), 1024px (lg), 1280px (xl)
- Sidebar collapses to hamburger menu on mobile
- Tables scroll horizontally on small screens or switch to card layout
- Touch-friendly tap targets (minimum 44x44px)
- No horizontal scroll on any viewport width
11. Accessibility (WCAG 2.1 AA)¶
- Semantic HTML: proper heading hierarchy, landmarks, labels
- ARIA attributes on interactive islands
- Keyboard navigation for all interactive elements
- Focus indicators visible on all focusable elements
- Colour contrast ratio minimum 4.5:1 (text), 3:1 (large text/UI)
- Screen reader testing with VoiceOver and NVDA
prefers-reduced-motionrespected for animations- Skip-to-content link on every page
12. API Client — src/lib/api.ts¶
export class ApiClient {
constructor(private baseUrl: string, private token: string) {}
async get<T>(path: string, params?: Record<string, string>): Promise<T>;
async post<T>(path: string, body: unknown): Promise<T>;
async put<T>(path: string, body: unknown): Promise<T>;
async delete(path: string): Promise<void>;
}
- Wraps
fetchwith automatic auth header injection - JSON serialisation/deserialisation
- Error mapping to user-friendly messages
- Request timeout (default 30s)
- Automatic token refresh on 401
13. Deployment¶
# Preview (PR branch)
npx wrangler pages deploy ./dist --project-name=simpaisa-portal --branch=$BRANCH
# Production
npx wrangler pages deploy ./dist --project-name=simpaisa-portal --branch=main
- Bitbucket Pipelines: lint → typecheck → test → build → deploy
- Preview URL generated for every PR
- Production deploy on merge to
main - Environment variables injected via Cloudflare dashboard (secrets for auth config)
14. Performance Targets¶
| Metric | Target |
|---|---|
| Lighthouse Performance | > 90 |
| First Contentful Paint | < 1.5s |
| Largest Contentful Paint | < 2.5s |
| Cumulative Layout Shift | < 0.1 |
| Total JS shipped (initial) | < 50KB gzipped |
- Astro ships zero JS by default; only island code is sent to client
- Fonts preloaded and subset to Latin + Arabic character sets
- Images optimised via Astro
<Image>component
15. Security¶
- CSP headers enforced via Cloudflare Pages
_headersfile - CSRF protection via double-submit cookie pattern
- XSS protection: auto-escaped templates (Astro default), DOMPurify for dynamic HTML
- Sensitive data (API keys) masked in UI, revealed on click
- Session timeout: 30 minutes idle, 8 hours absolute
- Audit log for sensitive actions (key creation, webhook changes)