540 lines
12 KiB
Markdown
540 lines
12 KiB
Markdown
|
|
# Templ Migration Guide
|
||
|
|
|
||
|
|
Convert HTML prototypes to Go `templ` templates with HTMX for server-driven UI.
|
||
|
|
|
||
|
|
## Architecture
|
||
|
|
|
||
|
|
```
|
||
|
|
Browser Go Server
|
||
|
|
------- ---------
|
||
|
|
[Web Awesome UI] <-- HTMX --> [Templ Renderer]
|
||
|
|
| |
|
||
|
|
| hx-post="/api/sign" |
|
||
|
|
| hx-target="#result" |
|
||
|
|
+--------------------------------->+
|
||
|
|
|
|
||
|
|
[Handler Logic]
|
||
|
|
|
|
||
|
|
+<---------------------------------+
|
||
|
|
| HTML Fragment Response
|
||
|
|
|
|
||
|
|
[DOM Update via HTMX]
|
||
|
|
```
|
||
|
|
|
||
|
|
## Directory Structure
|
||
|
|
|
||
|
|
```
|
||
|
|
project/
|
||
|
|
├── views/
|
||
|
|
│ ├── welcome.templ
|
||
|
|
│ ├── login.templ
|
||
|
|
│ ├── register.templ
|
||
|
|
│ ├── authorize.templ
|
||
|
|
│ ├── accounts.templ
|
||
|
|
│ ├── nfts.templ
|
||
|
|
│ └── service.templ
|
||
|
|
├── layouts/
|
||
|
|
│ ├── base.templ
|
||
|
|
│ ├── centered.templ # Auth pages
|
||
|
|
│ └── dashboard.templ # Authenticated pages
|
||
|
|
├── components/
|
||
|
|
│ ├── stepper.templ
|
||
|
|
│ ├── auth_method_card.templ
|
||
|
|
│ ├── permission_item.templ
|
||
|
|
│ ├── token_row.templ
|
||
|
|
│ ├── nft_card.templ
|
||
|
|
│ └── session_item.templ
|
||
|
|
└── handlers/
|
||
|
|
└── routes.go
|
||
|
|
```
|
||
|
|
|
||
|
|
## Page Overview
|
||
|
|
|
||
|
|
### Auth Pages (Popup/Webview Optimized)
|
||
|
|
|
||
|
|
| Page | File | Viewport | Description |
|
||
|
|
|------|------|----------|-------------|
|
||
|
|
| Welcome | `welcome.templ` | 360-480px | 3-step onboarding stepper |
|
||
|
|
| Login | `login.templ` | 360-480px | WebAuthn sign-in with passkey/security key/QR |
|
||
|
|
| Register | `register.templ` | 360-480px | Device detection + registration wizard |
|
||
|
|
| Authorize | `authorize.templ` | 360-480px | OAuth consent for connect/sign/transaction |
|
||
|
|
|
||
|
|
### Dashboard Pages
|
||
|
|
|
||
|
|
| Page | File | Description |
|
||
|
|
|------|------|-------------|
|
||
|
|
| Accounts | `accounts.templ` | Token balances, transactions, drawers |
|
||
|
|
| NFTs | `nfts.templ` | Gallery with collections, filters, actions |
|
||
|
|
| Service | `service.templ` | Connected service detail with permissions |
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Component Extraction
|
||
|
|
|
||
|
|
### Shared Components
|
||
|
|
|
||
|
|
```go
|
||
|
|
// components/stepper.templ
|
||
|
|
templ ProgressDots(current int, total int)
|
||
|
|
templ OnboardingStepper(current int, steps []string)
|
||
|
|
|
||
|
|
// components/auth_method_card.templ
|
||
|
|
templ AuthMethodCard(method, icon, title, desc string, disabled bool)
|
||
|
|
|
||
|
|
// components/permission_item.templ
|
||
|
|
templ PermissionItem(icon, iconClass, title, desc string)
|
||
|
|
|
||
|
|
// components/qr_section.templ
|
||
|
|
templ QRCodeSection(value, label, fallbackText string)
|
||
|
|
```
|
||
|
|
|
||
|
|
### Page-Specific Components
|
||
|
|
|
||
|
|
#### Login
|
||
|
|
```go
|
||
|
|
templ LoginStep1(caps DeviceCapabilities)
|
||
|
|
templ QRAuthStep()
|
||
|
|
templ RecoveryStep()
|
||
|
|
templ SuccessStep(title, redirectUrl string)
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Register
|
||
|
|
```go
|
||
|
|
templ DeviceDetectionStep(caps DeviceCapabilities)
|
||
|
|
templ RegistrationMethodStep(method string)
|
||
|
|
templ RegistrationSuccessStep()
|
||
|
|
templ CapabilityItem(id, label string, supported, loading bool)
|
||
|
|
templ RecoveryKeyDisplay(publicKey, recoveryKey string)
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Authorize
|
||
|
|
```go
|
||
|
|
templ AppIdentityHeader(app AppInfo)
|
||
|
|
templ WalletSelector(wallets []Wallet, selected string)
|
||
|
|
templ PermissionList(permissions []Permission)
|
||
|
|
templ MessagePreview(message, signType string)
|
||
|
|
templ TransactionPreview(tx Transaction)
|
||
|
|
templ SwapPreview(from, to TokenAmount)
|
||
|
|
templ GasEstimate(network, gas, maxFee string)
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Accounts
|
||
|
|
```go
|
||
|
|
templ StatsGrid(stats WalletStats)
|
||
|
|
templ TokenBalancesCard(tokens []Token)
|
||
|
|
templ TokenRow(token Token)
|
||
|
|
templ RecentTransactionsCard(txs []Transaction)
|
||
|
|
templ TransactionRow(tx Transaction)
|
||
|
|
templ ConnectedAccountsCard(accounts []Account)
|
||
|
|
templ ReceiveDrawer(token string)
|
||
|
|
templ SendDrawer(tokens []Token)
|
||
|
|
templ SwapDrawer(tokens []Token)
|
||
|
|
templ CreateAccountDialog(step int, network string)
|
||
|
|
```
|
||
|
|
|
||
|
|
#### NFTs
|
||
|
|
```go
|
||
|
|
templ NFTsStatsGrid(stats NFTStats)
|
||
|
|
templ CollectionsCard(collections []Collection)
|
||
|
|
templ CollectionCard(collection Collection)
|
||
|
|
templ NFTsGalleryCard(nfts []NFTItem, filter NFTFilter)
|
||
|
|
templ NFTFilterBar(filter NFTFilter)
|
||
|
|
templ NFTGrid(nfts []NFTItem)
|
||
|
|
templ NFTCard(nft NFTItem)
|
||
|
|
templ NFTBadge(badgeType string)
|
||
|
|
```
|
||
|
|
|
||
|
|
#### Service
|
||
|
|
```go
|
||
|
|
templ ServiceHero(info ServiceInfo, status ServiceStatus)
|
||
|
|
templ UsageStatsGrid(stats UsageStats)
|
||
|
|
templ PermissionsCard(permissions []Permission)
|
||
|
|
templ PermissionToggle(scope string, enabled bool, desc string)
|
||
|
|
templ ServiceInfoCard(oauth OAuthInfo)
|
||
|
|
templ SessionLogCard(sessions []SessionEvent)
|
||
|
|
templ SessionLogItem(event SessionEvent)
|
||
|
|
templ DataAccessCard(access DataAccess)
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## State Types
|
||
|
|
|
||
|
|
### Auth Pages
|
||
|
|
|
||
|
|
```go
|
||
|
|
type DeviceCapabilities struct {
|
||
|
|
PlatformAuth bool // Face ID, Touch ID, Windows Hello
|
||
|
|
CrossPlatform bool // Security keys (YubiKey)
|
||
|
|
ConditionalUI bool // Passkey autofill support
|
||
|
|
}
|
||
|
|
|
||
|
|
type LoginState struct {
|
||
|
|
Step string
|
||
|
|
SelectedMethod string // "passkey", "security-key", "qr-code"
|
||
|
|
Username string
|
||
|
|
Error string
|
||
|
|
}
|
||
|
|
|
||
|
|
type RegisterState struct {
|
||
|
|
Step int
|
||
|
|
SelectedMethod string
|
||
|
|
Username string
|
||
|
|
DisplayName string
|
||
|
|
PublicKey string
|
||
|
|
RecoveryKey string
|
||
|
|
Error string
|
||
|
|
}
|
||
|
|
|
||
|
|
type AuthRequest struct {
|
||
|
|
Type string // "connect", "sign", "transaction"
|
||
|
|
App AppInfo
|
||
|
|
Scopes []string
|
||
|
|
Message string
|
||
|
|
Transaction *TxDetails
|
||
|
|
State string
|
||
|
|
Nonce string
|
||
|
|
}
|
||
|
|
|
||
|
|
type AppInfo struct {
|
||
|
|
Name string
|
||
|
|
Domain string
|
||
|
|
LogoURL string
|
||
|
|
Verified bool
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Dashboard Pages
|
||
|
|
|
||
|
|
```go
|
||
|
|
type WalletData struct {
|
||
|
|
Stats WalletStats
|
||
|
|
Tokens []Token
|
||
|
|
Transactions []Transaction
|
||
|
|
Accounts []Account
|
||
|
|
}
|
||
|
|
|
||
|
|
type WalletStats struct {
|
||
|
|
TotalBalance float64
|
||
|
|
Change24h float64
|
||
|
|
ChangePercent float64
|
||
|
|
AssetCount int
|
||
|
|
PendingCount int
|
||
|
|
}
|
||
|
|
|
||
|
|
type Token struct {
|
||
|
|
Symbol string
|
||
|
|
Name string
|
||
|
|
Balance float64
|
||
|
|
ValueUSD float64
|
||
|
|
IconColor string
|
||
|
|
}
|
||
|
|
|
||
|
|
type NFTsData struct {
|
||
|
|
Stats NFTStats
|
||
|
|
Collections []Collection
|
||
|
|
NFTs []NFTItem
|
||
|
|
Filter NFTFilter
|
||
|
|
}
|
||
|
|
|
||
|
|
type NFTFilter struct {
|
||
|
|
Tab string // "all", "listed", "unlisted"
|
||
|
|
Collection string
|
||
|
|
Sort string // "recent", "value-high", "value-low", "name"
|
||
|
|
View string // "grid", "list"
|
||
|
|
}
|
||
|
|
|
||
|
|
type ServiceData struct {
|
||
|
|
Info ServiceInfo
|
||
|
|
Status ServiceStatus
|
||
|
|
Stats UsageStats
|
||
|
|
Permissions []Permission
|
||
|
|
OAuth OAuthInfo
|
||
|
|
Sessions []SessionEvent
|
||
|
|
DataAccess DataAccess
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## HTMX Patterns
|
||
|
|
|
||
|
|
### Step Navigation
|
||
|
|
|
||
|
|
```html
|
||
|
|
<!-- Before: JavaScript -->
|
||
|
|
<wa-button onclick="goToStep(2)">Next</wa-button>
|
||
|
|
|
||
|
|
<!-- After: HTMX -->
|
||
|
|
<wa-button hx-get="/welcome/step/2" hx-target="#step-content">Next</wa-button>
|
||
|
|
```
|
||
|
|
|
||
|
|
### Form Submission
|
||
|
|
|
||
|
|
```html
|
||
|
|
<form hx-post="/api/auth/register"
|
||
|
|
hx-target="#step-content"
|
||
|
|
hx-indicator="#register-spinner">
|
||
|
|
```
|
||
|
|
|
||
|
|
### Auth Method Selection
|
||
|
|
|
||
|
|
```html
|
||
|
|
<div class="auth-method-card"
|
||
|
|
hx-post="/login/select-method"
|
||
|
|
hx-vals='{"method":"passkey"}'
|
||
|
|
hx-target="#btn-signin"
|
||
|
|
hx-swap="outerHTML">
|
||
|
|
```
|
||
|
|
|
||
|
|
### QR Code Polling
|
||
|
|
|
||
|
|
```html
|
||
|
|
<div id="qr-status"
|
||
|
|
hx-get="/api/auth/qr-status?session=xyz789"
|
||
|
|
hx-trigger="every 2s"
|
||
|
|
hx-target="this">
|
||
|
|
```
|
||
|
|
|
||
|
|
### Tab Switching
|
||
|
|
|
||
|
|
```html
|
||
|
|
<wa-tab-group hx-on:wa-tab-show="
|
||
|
|
htmx.ajax('GET', '/authorize/panel/' + event.detail.name, {target: '#panel-content'})
|
||
|
|
">
|
||
|
|
```
|
||
|
|
|
||
|
|
### Drawer Opening
|
||
|
|
|
||
|
|
```html
|
||
|
|
<wa-button hx-get="/drawers/receive"
|
||
|
|
hx-target="#drawer-container"
|
||
|
|
hx-swap="innerHTML">
|
||
|
|
Receive
|
||
|
|
</wa-button>
|
||
|
|
```
|
||
|
|
|
||
|
|
### Filter with URL State
|
||
|
|
|
||
|
|
```html
|
||
|
|
<wa-tab-group hx-on:wa-tab-show="
|
||
|
|
htmx.ajax('GET', '/nfts?filter=' + event.detail.name, {target: '#nft-grid', pushUrl: true})
|
||
|
|
">
|
||
|
|
```
|
||
|
|
|
||
|
|
### Permission Toggle
|
||
|
|
|
||
|
|
```html
|
||
|
|
<wa-switch hx-post="/api/connections/{id}/scopes"
|
||
|
|
hx-vals='{"scope": "wallet:sign", "enabled": !this.checked}'
|
||
|
|
hx-target="#permission-status"
|
||
|
|
hx-swap="innerHTML">
|
||
|
|
```
|
||
|
|
|
||
|
|
### Out-of-Band Updates
|
||
|
|
|
||
|
|
```go
|
||
|
|
templ TransactionResult(tx Transaction) {
|
||
|
|
<div id="tx-result">
|
||
|
|
<wa-callout variant="success">
|
||
|
|
Transaction submitted: { tx.Hash }
|
||
|
|
</wa-callout>
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<div id="balance-display" hx-swap-oob="true">
|
||
|
|
@BalanceCard(tx.NewBalance)
|
||
|
|
</div>
|
||
|
|
|
||
|
|
<wa-drawer id="send-drawer" hx-swap-oob="true"></wa-drawer>
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Stats Auto-Refresh
|
||
|
|
|
||
|
|
```html
|
||
|
|
<div id="stats-grid"
|
||
|
|
hx-get="/api/wallet/stats"
|
||
|
|
hx-trigger="every 30s"
|
||
|
|
hx-swap="innerHTML">
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Layout Templates
|
||
|
|
|
||
|
|
### Base Layout
|
||
|
|
|
||
|
|
```go
|
||
|
|
// layouts/base.templ
|
||
|
|
templ Base(title string) {
|
||
|
|
<!DOCTYPE html>
|
||
|
|
<html lang="en" class="wa-cloak">
|
||
|
|
<head>
|
||
|
|
<meta charset="utf-8"/>
|
||
|
|
<meta name="viewport" content="width=device-width, initial-scale=1"/>
|
||
|
|
<title>{ title } - Sonr Motr Wallet</title>
|
||
|
|
<script src="https://cdn.sonr.org/wa/autoloader.js"></script>
|
||
|
|
</head>
|
||
|
|
<body>
|
||
|
|
{ children... }
|
||
|
|
</body>
|
||
|
|
</html>
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Centered Card (Auth Pages)
|
||
|
|
|
||
|
|
```go
|
||
|
|
// layouts/centered.templ
|
||
|
|
templ CenteredCard(title string) {
|
||
|
|
@Base(title) {
|
||
|
|
<wa-page>
|
||
|
|
<main class="main-centered">
|
||
|
|
<wa-card>
|
||
|
|
{ children... }
|
||
|
|
</wa-card>
|
||
|
|
</main>
|
||
|
|
</wa-page>
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Dashboard Layout
|
||
|
|
|
||
|
|
```go
|
||
|
|
// layouts/dashboard.templ
|
||
|
|
templ DashboardLayout(activePage string) {
|
||
|
|
@Base("Dashboard") {
|
||
|
|
<wa-page>
|
||
|
|
<div class="dashboard-layout">
|
||
|
|
@Sidebar(activePage)
|
||
|
|
<main class="main-content">
|
||
|
|
{ children... }
|
||
|
|
</main>
|
||
|
|
</div>
|
||
|
|
</wa-page>
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Viewport Constraints
|
||
|
|
|
||
|
|
Auth pages optimized for popup windows and webviews:
|
||
|
|
|
||
|
|
| Size | Dimensions | Use Case |
|
||
|
|
|------|------------|----------|
|
||
|
|
| Small | 360 x 540 | Mobile popup |
|
||
|
|
| Medium | 420 x 600 | Default popup |
|
||
|
|
| Large | 480 x 680 | Desktop popup |
|
||
|
|
| Mobile | 375 x 667 | iPhone viewport |
|
||
|
|
|
||
|
|
CSS breakpoints:
|
||
|
|
```css
|
||
|
|
@media (max-width: 400px) {
|
||
|
|
.main-centered { padding: var(--wa-space-m); }
|
||
|
|
.main-centered wa-card { max-width: 100%; }
|
||
|
|
}
|
||
|
|
|
||
|
|
@media (max-height: 600px) {
|
||
|
|
.hero-icon { width: 56px; height: 56px; }
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Migration Checklist
|
||
|
|
|
||
|
|
1. **Layouts first**
|
||
|
|
- [ ] `base.templ` - HTML head, scripts, body wrapper
|
||
|
|
- [ ] `centered.templ` - Auth pages card layout
|
||
|
|
- [ ] `dashboard.templ` - Sidebar + main content
|
||
|
|
|
||
|
|
2. **Shared components**
|
||
|
|
- [ ] Stepper/Progress dots
|
||
|
|
- [ ] Auth method cards
|
||
|
|
- [ ] Permission items
|
||
|
|
- [ ] QR code sections
|
||
|
|
- [ ] Token rows
|
||
|
|
- [ ] NFT cards
|
||
|
|
|
||
|
|
3. **Auth pages**
|
||
|
|
- [ ] `welcome.templ` - Onboarding flow
|
||
|
|
- [ ] `login.templ` - Sign-in with WebAuthn
|
||
|
|
- [ ] `register.templ` - Registration wizard
|
||
|
|
- [ ] `authorize.templ` - OAuth consent
|
||
|
|
|
||
|
|
4. **Dashboard pages**
|
||
|
|
- [ ] `accounts.templ` - Main wallet view
|
||
|
|
- [ ] `nfts.templ` - NFT gallery
|
||
|
|
- [ ] `service.templ` - Connected service detail
|
||
|
|
|
||
|
|
5. **Wire HTMX**
|
||
|
|
- [ ] Replace all `onclick` handlers with `hx-*` attributes
|
||
|
|
- [ ] Add polling for real-time updates
|
||
|
|
- [ ] Implement drawer/dialog triggers
|
||
|
|
|
||
|
|
6. **Connect handlers**
|
||
|
|
- [ ] Route registration
|
||
|
|
- [ ] API endpoints for HTMX partials
|
||
|
|
- [ ] WebAuthn integration points
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## CSS Variables
|
||
|
|
|
||
|
|
Primary brand color:
|
||
|
|
```css
|
||
|
|
:root {
|
||
|
|
--wa-color-primary: #17c2ff;
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Layout patterns:
|
||
|
|
```css
|
||
|
|
.main-centered {
|
||
|
|
display: flex;
|
||
|
|
justify-content: center;
|
||
|
|
align-items: center;
|
||
|
|
min-height: 100%;
|
||
|
|
padding: var(--wa-space-l);
|
||
|
|
box-sizing: border-box;
|
||
|
|
}
|
||
|
|
|
||
|
|
.main-centered wa-card {
|
||
|
|
width: 100%;
|
||
|
|
max-width: 45ch; /* Auth pages */
|
||
|
|
max-width: 48ch; /* Wider content */
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Web Awesome Components Reference
|
||
|
|
|
||
|
|
### Layout Classes
|
||
|
|
- `wa-stack` - Vertical flex with `wa-gap-{size}`
|
||
|
|
- `wa-cluster` - Horizontal flex with `wa-gap-{size}`
|
||
|
|
- `wa-grid` - CSS grid with `--min-column-size`
|
||
|
|
- `wa-flank` - Space-between horizontal layout
|
||
|
|
|
||
|
|
### Gap Sizes
|
||
|
|
`3xs`, `2xs`, `xs`, `s`, `m`, `l`, `xl`, `2xl`, `3xl`
|
||
|
|
|
||
|
|
### Typography
|
||
|
|
- Headings: `wa-heading-xs` through `wa-heading-2xl`
|
||
|
|
- Captions: `wa-caption-xs`, `wa-caption-s`, `wa-caption-m`
|
||
|
|
- Labels: `wa-label-s`, `wa-label-m`
|
||
|
|
|
||
|
|
### Common Components
|
||
|
|
```html
|
||
|
|
<wa-button variant="brand">Primary</wa-button>
|
||
|
|
<wa-button variant="neutral" appearance="outlined">Secondary</wa-button>
|
||
|
|
<wa-input label="Field" placeholder="Enter value"></wa-input>
|
||
|
|
<wa-card><div slot="header">Title</div>Content</wa-card>
|
||
|
|
<wa-drawer label="Title" placement="end"></wa-drawer>
|
||
|
|
<wa-dialog label="Title"></wa-dialog>
|
||
|
|
<wa-callout variant="warning"><wa-icon slot="icon" name="alert"></wa-icon>Message</wa-callout>
|
||
|
|
```
|