593 lines
19 KiB
Markdown
593 lines
19 KiB
Markdown
# Milestone 5: Developer Portal Frontend
|
|
|
|
**Status**: Decided
|
|
**Goal**: Web interface for developer account and app management.
|
|
|
|
## Decision
|
|
|
|
**htmx + Go Templates** for server-rendered UI from the single Go container:
|
|
|
|
```
|
|
Rendering: Go html/template
|
|
Interactivity: htmx (partial page updates)
|
|
Styling: Tailwind CSS (compiled at build time)
|
|
Charts: Chart.js (lightweight)
|
|
Icons: Heroicons or Lucide
|
|
Build: Embed static assets in Go binary
|
|
```
|
|
|
|
### Rationale
|
|
|
|
1. **Single container** - No separate Node.js server needed
|
|
2. **Server-rendered** - All HTML generated by Go templates
|
|
3. **htmx for interactivity** - AJAX without JavaScript framework
|
|
4. **Embedded assets** - CSS/JS bundled into Go binary via `embed`
|
|
5. **Simple deployment** - Just the Go binary, nothing else
|
|
6. **Low resource usage** - Perfect for Synology NAS
|
|
|
|
### Architecture
|
|
|
|
```
|
|
┌─────────────────────────────────────────┐
|
|
│ mosis-portal container │
|
|
│ ┌────────────────────────────────────┐ │
|
|
│ │ Go binary │ │
|
|
│ │ ├── Chi router │ │
|
|
│ │ ├── html/template rendering │ │
|
|
│ │ ├── Embedded static assets │ │
|
|
│ │ │ ├── tailwind.css (built) │ │
|
|
│ │ │ ├── htmx.min.js │ │
|
|
│ │ │ └── chart.min.js │ │
|
|
│ │ └── SQLite database │ │
|
|
│ └────────────────────────────────────┘ │
|
|
└─────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Overview
|
|
|
|
The developer portal is the primary interface for developers to manage their accounts, create apps, submit versions, and view analytics.
|
|
|
|
---
|
|
|
|
## Pages Required
|
|
|
|
### Public Pages
|
|
|
|
| Page | URL | Purpose |
|
|
|------|-----|---------|
|
|
| Landing | `/` | Marketing, sign up CTA |
|
|
| Sign In | `/login` | OAuth + password login |
|
|
| Sign Up | `/register` | Create account |
|
|
| Docs | `/docs/*` | Documentation (separate site?) |
|
|
|
|
### Authenticated Pages
|
|
|
|
| Page | URL | Purpose |
|
|
|------|-----|---------|
|
|
| Dashboard | `/dashboard` | App list, quick stats |
|
|
| App Details | `/apps/:id` | Single app overview |
|
|
| App Settings | `/apps/:id/settings` | Edit app metadata |
|
|
| App Versions | `/apps/:id/versions` | Version history |
|
|
| Submit Version | `/apps/:id/versions/new` | Upload new version |
|
|
| App Analytics | `/apps/:id/analytics` | Telemetry dashboard |
|
|
| Create App | `/apps/new` | New app wizard |
|
|
| API Keys | `/settings/keys` | Manage API keys |
|
|
| Signing Keys | `/settings/signing` | Manage signing keys |
|
|
| Profile | `/settings/profile` | Account settings |
|
|
|
|
---
|
|
|
|
## Wireframes
|
|
|
|
### Dashboard
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ [Logo] Dashboard Apps Docs Settings [Avatar ▼] │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ Welcome back, Developer! │
|
|
│ │
|
|
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │
|
|
│ │ Total Apps │ │ Downloads │ │ Active Users│ │
|
|
│ │ 12 │ │ 45,230 │ │ 1,234 │ │
|
|
│ └─────────────┘ └─────────────┘ └─────────────┘ │
|
|
│ │
|
|
│ Your Apps [+ New App] │
|
|
│ ┌──────────────────────────────────────────────────────┐ │
|
|
│ │ [Icon] My Calculator v1.2.0 Published 1.2K ↓ │ │
|
|
│ │ [Icon] Notes App v2.0.1 Published 5.4K ↓ │ │
|
|
│ │ [Icon] Weather Widget v0.9.0 In Review --- │ │
|
|
│ └──────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### App Details
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ ← Back to Apps │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ [Icon] My Calculator │
|
|
│ com.developer.calculator │
|
|
│ ● Published │
|
|
│ │
|
|
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
|
│ │ Overview │ │ Versions │ │Analytics │ │ Settings │ │
|
|
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
|
|
│ │
|
|
│ Latest Version: 1.2.0 [Submit New Version] │
|
|
│ Published: Jan 15, 2024 │
|
|
│ Downloads: 1,234 │
|
|
│ │
|
|
│ ┌────────────────────────────────────────┐ │
|
|
│ │ Downloads over time │ │
|
|
│ │ [Chart: line graph] │ │
|
|
│ └────────────────────────────────────────┘ │
|
|
│ │
|
|
│ Recent Crashes [View All →] │
|
|
│ • attempt to index nil (v1.2.0) - 23 reports │
|
|
│ • memory limit exceeded (v1.1.0) - 5 reports │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Submit Version
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ Submit New Version - My Calculator │
|
|
├─────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ Step 2 of 3: Upload Package │
|
|
│ ○ Details ● Upload ○ Review │
|
|
│ │
|
|
│ ┌─────────────────────────────────────────────────────┐ │
|
|
│ │ │ │
|
|
│ │ ┌─────────┐ │ │
|
|
│ │ │ .mosis │ Drop your package here │ │
|
|
│ │ │ 📦 │ or click to browse │ │
|
|
│ │ └─────────┘ │ │
|
|
│ │ │ │
|
|
│ │ Max size: 50 MB │ │
|
|
│ │ │ │
|
|
│ └─────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ✓ Package validated │
|
|
│ ✓ Signature verified │
|
|
│ ✓ Permissions: storage, network │
|
|
│ │
|
|
│ [Back] [Continue to Review] │
|
|
│ │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Tech Stack Options
|
|
|
|
### Option A: Next.js 14
|
|
|
|
```
|
|
Framework: Next.js 14 (App Router)
|
|
UI: shadcn/ui + Tailwind CSS
|
|
State: React Query + Zustand
|
|
Forms: React Hook Form + Zod
|
|
Charts: Recharts or Tremor
|
|
Auth: NextAuth.js
|
|
```
|
|
|
|
| Pros | Cons |
|
|
|------|------|
|
|
| SSR for SEO landing page | Complexity |
|
|
| Great developer experience | React knowledge required |
|
|
| Full-stack capability | Heavier bundle |
|
|
| Large ecosystem | |
|
|
|
|
### Option B: SvelteKit
|
|
|
|
```
|
|
Framework: SvelteKit
|
|
UI: Skeleton UI or custom
|
|
State: Svelte stores
|
|
Forms: Superforms
|
|
Charts: Chart.js or LayerCake
|
|
Auth: Lucia
|
|
```
|
|
|
|
| Pros | Cons |
|
|
|------|------|
|
|
| Fast, small bundles | Smaller ecosystem |
|
|
| Simple state management | Fewer UI libraries |
|
|
| Great DX | Less hiring pool |
|
|
|
|
### Option C: Astro + React Islands
|
|
|
|
```
|
|
Framework: Astro
|
|
UI: React components (islands)
|
|
State: Nanostores
|
|
Forms: React Hook Form
|
|
Charts: Recharts
|
|
Auth: Custom
|
|
```
|
|
|
|
| Pros | Cons |
|
|
|------|------|
|
|
| Ultra-fast static pages | More setup |
|
|
| Partial hydration | Newer approach |
|
|
| Use React where needed | Less documented patterns |
|
|
|
|
### Option D: htmx + Go Templates
|
|
|
|
```
|
|
Framework: Go templates + htmx
|
|
UI: Tailwind CSS
|
|
State: Server-side
|
|
Forms: Native HTML
|
|
Charts: Chart.js
|
|
Auth: Server sessions
|
|
```
|
|
|
|
| Pros | Cons |
|
|
|------|------|
|
|
| Simple, fast | Limited interactivity |
|
|
| No JS framework | Less polished UX |
|
|
| Server-rendered | Complex UI harder |
|
|
|
|
---
|
|
|
|
## UI Component Library
|
|
|
|
### Option A: shadcn/ui
|
|
|
|
```
|
|
Base: Radix UI primitives
|
|
Styling: Tailwind CSS
|
|
Approach: Copy-paste components
|
|
```
|
|
|
|
| Pros | Cons |
|
|
|------|------|
|
|
| High quality | React only |
|
|
| Full control | Manual updates |
|
|
| Accessible | |
|
|
|
|
### Option B: Tailwind UI
|
|
|
|
```
|
|
Base: Headless UI
|
|
Styling: Tailwind CSS
|
|
Approach: Copy-paste templates
|
|
```
|
|
|
|
| Pros | Cons |
|
|
|------|------|
|
|
| Beautiful designs | Paid ($299) |
|
|
| Production-ready | Templates, not components |
|
|
|
|
### Option C: Mantine
|
|
|
|
```
|
|
Base: Custom components
|
|
Styling: CSS-in-JS or CSS
|
|
Approach: npm package
|
|
```
|
|
|
|
| Pros | Cons |
|
|
|------|------|
|
|
| Complete solution | Opinionated |
|
|
| Many components | Larger bundle |
|
|
| Good docs | |
|
|
|
|
---
|
|
|
|
## Key Features (htmx Implementation)
|
|
|
|
### File Upload with Progress
|
|
|
|
```html
|
|
<!-- htmx file upload with progress indicator -->
|
|
<form hx-post="/api/apps/{{.App.ID}}/versions"
|
|
hx-encoding="multipart/form-data"
|
|
hx-target="#upload-result"
|
|
hx-indicator="#upload-spinner">
|
|
|
|
<div class="dropzone" id="dropzone">
|
|
<input type="file" name="package" accept=".mosis" required
|
|
class="hidden" id="file-input">
|
|
<label for="file-input" class="cursor-pointer">
|
|
<svg><!-- upload icon --></svg>
|
|
<p>Drop your .mosis package here or click to browse</p>
|
|
<p class="text-sm text-gray-500">Max size: 50 MB</p>
|
|
</label>
|
|
</div>
|
|
|
|
<div id="upload-spinner" class="htmx-indicator">
|
|
Uploading...
|
|
</div>
|
|
|
|
<button type="submit" class="btn btn-primary">Upload</button>
|
|
</form>
|
|
|
|
<div id="upload-result"></div>
|
|
```
|
|
|
|
### Dynamic App List
|
|
|
|
```html
|
|
<!-- Server renders this, htmx updates it -->
|
|
<div id="app-list"
|
|
hx-get="/partials/apps"
|
|
hx-trigger="load, newApp from:body"
|
|
hx-swap="innerHTML">
|
|
{{range .Apps}}
|
|
<div class="app-card">
|
|
<img src="{{.IconURL}}" alt="{{.Name}}">
|
|
<h3>{{.Name}}</h3>
|
|
<span class="badge {{.StatusClass}}">{{.Status}}</span>
|
|
<a href="/apps/{{.ID}}" hx-boost="true">View →</a>
|
|
</div>
|
|
{{end}}
|
|
</div>
|
|
```
|
|
|
|
### Form with Validation
|
|
|
|
```html
|
|
<!-- Create app form with server-side validation -->
|
|
<form hx-post="/apps" hx-target="#form-result" hx-swap="outerHTML">
|
|
<div class="form-group">
|
|
<label for="name">App Name</label>
|
|
<input type="text" name="name" id="name" required
|
|
hx-post="/api/validate/name"
|
|
hx-trigger="blur"
|
|
hx-target="next .error">
|
|
<span class="error"></span>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="package_id">Package ID</label>
|
|
<input type="text" name="package_id" id="package_id"
|
|
placeholder="com.yourname.appname" required
|
|
hx-post="/api/validate/package-id"
|
|
hx-trigger="blur"
|
|
hx-target="next .error">
|
|
<span class="error"></span>
|
|
</div>
|
|
|
|
<button type="submit" class="btn btn-primary">Create App</button>
|
|
</form>
|
|
```
|
|
|
|
### Analytics Chart
|
|
|
|
```html
|
|
<!-- Chart.js for analytics -->
|
|
<div class="chart-container">
|
|
<canvas id="downloads-chart"></canvas>
|
|
</div>
|
|
|
|
<script>
|
|
// Data injected from Go template
|
|
const chartData = {{.ChartDataJSON}};
|
|
|
|
new Chart(document.getElementById('downloads-chart'), {
|
|
type: 'line',
|
|
data: {
|
|
labels: chartData.labels,
|
|
datasets: [{
|
|
label: 'Downloads',
|
|
data: chartData.values,
|
|
borderColor: '#6366f1',
|
|
tension: 0.3
|
|
}]
|
|
}
|
|
});
|
|
</script>
|
|
```
|
|
|
|
---
|
|
|
|
## Go Template Structure
|
|
|
|
### Base Layout
|
|
|
|
```go
|
|
// templates/layouts/base.html
|
|
{{define "base"}}
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>{{.Title}} - Mosis Developer Portal</title>
|
|
<link href="/static/tailwind.css" rel="stylesheet">
|
|
<script src="/static/htmx.min.js"></script>
|
|
</head>
|
|
<body class="bg-gray-50" hx-boost="true">
|
|
{{template "navbar" .}}
|
|
<main class="container mx-auto py-8">
|
|
{{template "content" .}}
|
|
</main>
|
|
</body>
|
|
</html>
|
|
{{end}}
|
|
```
|
|
|
|
### Page Template
|
|
|
|
```go
|
|
// templates/pages/dashboard.html
|
|
{{define "content"}}
|
|
<h1 class="text-2xl font-bold mb-6">Dashboard</h1>
|
|
|
|
<div class="grid grid-cols-3 gap-6 mb-8">
|
|
{{template "stat-card" dict "Label" "Total Apps" "Value" .Stats.TotalApps}}
|
|
{{template "stat-card" dict "Label" "Downloads" "Value" .Stats.Downloads}}
|
|
{{template "stat-card" dict "Label" "Active Users" "Value" .Stats.ActiveUsers}}
|
|
</div>
|
|
|
|
<div class="flex justify-between items-center mb-4">
|
|
<h2 class="text-xl font-semibold">Your Apps</h2>
|
|
<a href="/apps/new" class="btn btn-primary">+ New App</a>
|
|
</div>
|
|
|
|
<div id="app-list" hx-get="/partials/apps" hx-trigger="load">
|
|
Loading...
|
|
</div>
|
|
{{end}}
|
|
```
|
|
|
|
---
|
|
|
|
## Authentication Flow
|
|
|
|
### Login Page (Go Template)
|
|
|
|
```html
|
|
{{define "content"}}
|
|
<div class="min-h-screen flex items-center justify-center">
|
|
<div class="card w-96 bg-white shadow-lg rounded-lg p-6">
|
|
<h1 class="text-2xl font-bold text-center mb-6">Sign in to Mosis</h1>
|
|
|
|
<div class="space-y-4">
|
|
<a href="/auth/github" class="btn btn-github w-full flex items-center justify-center gap-2">
|
|
<svg><!-- GitHub icon --></svg>
|
|
Continue with GitHub
|
|
</a>
|
|
|
|
<a href="/auth/google" class="btn btn-google w-full flex items-center justify-center gap-2">
|
|
<svg><!-- Google icon --></svg>
|
|
Continue with Google
|
|
</a>
|
|
|
|
<div class="divider">or</div>
|
|
|
|
<form hx-post="/auth/login" hx-target="#login-error" class="space-y-4">
|
|
<input type="email" name="email" placeholder="Email"
|
|
class="input input-bordered w-full" required>
|
|
<input type="password" name="password" placeholder="Password"
|
|
class="input input-bordered w-full" required>
|
|
<div id="login-error" class="text-red-500 text-sm"></div>
|
|
<button type="submit" class="btn btn-primary w-full">Sign in</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{{end}}
|
|
```
|
|
|
|
### Auth Middleware (Go)
|
|
|
|
```go
|
|
// middleware/auth.go
|
|
func RequireAuth(next http.Handler) http.Handler {
|
|
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
|
session, err := sessionStore.Get(r, "session")
|
|
if err != nil || session.Values["developer_id"] == nil {
|
|
http.Redirect(w, r, "/login", http.StatusSeeOther)
|
|
return
|
|
}
|
|
next.ServeHTTP(w, r)
|
|
})
|
|
}
|
|
|
|
// Usage in router
|
|
r.Group(func(r chi.Router) {
|
|
r.Use(RequireAuth)
|
|
r.Get("/dashboard", handlers.Dashboard)
|
|
r.Get("/apps", handlers.AppList)
|
|
r.Get("/apps/{id}", handlers.AppDetail)
|
|
})
|
|
```
|
|
|
|
---
|
|
|
|
## Responsive Design
|
|
|
|
### Breakpoints
|
|
|
|
| Size | Width | Target |
|
|
|------|-------|--------|
|
|
| sm | 640px | Mobile landscape |
|
|
| md | 768px | Tablet |
|
|
| lg | 1024px | Desktop |
|
|
| xl | 1280px | Large desktop |
|
|
|
|
### Mobile Navigation
|
|
|
|
```tsx
|
|
// Hamburger menu for mobile
|
|
<Sheet>
|
|
<SheetTrigger className="md:hidden">
|
|
<MenuIcon />
|
|
</SheetTrigger>
|
|
<SheetContent side="left">
|
|
<Navigation />
|
|
</SheetContent>
|
|
</Sheet>
|
|
```
|
|
|
|
---
|
|
|
|
## Accessibility
|
|
|
|
### Requirements
|
|
|
|
- [ ] Keyboard navigation
|
|
- [ ] Screen reader support
|
|
- [ ] Color contrast (WCAG AA)
|
|
- [ ] Focus indicators
|
|
- [ ] Alt text for images
|
|
- [ ] Form labels
|
|
- [ ] Error announcements
|
|
|
|
### Testing
|
|
|
|
```bash
|
|
# Lighthouse audit
|
|
npx lighthouse http://localhost:3000 --view
|
|
|
|
# axe-core
|
|
npm install @axe-core/react
|
|
```
|
|
|
|
---
|
|
|
|
## Deliverables
|
|
|
|
- [x] Framework selection (htmx + Go Templates)
|
|
- [x] UI component library selection (Tailwind CSS + Chart.js)
|
|
- [ ] Design system (colors, typography)
|
|
- [x] Page wireframes (see above)
|
|
- [x] Authentication flow (server sessions + OAuth)
|
|
- [ ] Dashboard implementation
|
|
- [ ] App management pages
|
|
- [ ] Version submission flow
|
|
- [ ] Settings pages
|
|
- [ ] Responsive design
|
|
- [ ] Accessibility audit
|
|
|
|
---
|
|
|
|
## Open Questions
|
|
|
|
1. ~~Dark mode support?~~ → Defer to post-MVP (Tailwind makes it easy to add later)
|
|
2. ~~Internationalization (i18n)?~~ → English only for MVP
|
|
3. ~~Custom domain for docs vs integrated?~~ → Integrated into same Go server at /docs
|
|
4. Email notifications UI? → Consider for v1.1
|
|
|
|
---
|
|
|
|
## References
|
|
|
|
- [shadcn/ui](https://ui.shadcn.com/)
|
|
- [Next.js App Router](https://nextjs.org/docs/app)
|
|
- [Tailwind CSS](https://tailwindcss.com/)
|
|
- [React Query](https://tanstack.com/query/latest)
|