AgentSkillsCN

Bkper Web Dev

Bkper Web 开发

SKILL.md

Bkper Web Development

Knowledge for building web interfaces for Bkper apps using @bkper/web-* packages, Lit components, and Hono.

Web SDK Packages

PackagePurpose
@bkper/web-authOAuth authentication client
@bkper/web-designCSS design tokens and theming

Authentication (@bkper/web-auth)

Installation

bash
bun add @bkper/web-auth

Basic Usage

typescript
import { BkperAuth } from '@bkper/web-auth';

const auth = new BkperAuth({
  onLoginSuccess: () => {
    // User is authenticated, load app data
    loadUserData();
  },
  onLoginRequired: () => {
    // Show login button/UI
    showLoginButton();
  },
  onError: (error) => {
    console.error('Auth error:', error);
  },
  onTokenRefresh: (token) => {
    // Token refreshed, update API client
    updateApiClient(token);
  },
  onLogout: () => {
    // Clean up before redirect
    clearLocalState();
  },
});

// Initialize on app load (restores session)
await auth.init();

API Reference

typescript
// Get current access token (undefined if not authenticated)
const token = auth.getAccessToken();

// Redirect to login page
auth.login();

// Refresh access token (call on 403)
await auth.refresh();

// Logout and redirect
auth.logout();

Using with Fetch

typescript
async function apiFetch(url: string, options: RequestInit = {}) {
  const token = auth.getAccessToken();
  
  const response = await fetch(url, {
    ...options,
    headers: {
      ...options.headers,
      'Authorization': `Bearer ${token}`,
    },
  });

  // Handle token expiration
  if (response.status === 403) {
    await auth.refresh();
    return apiFetch(url, options); // Retry with new token
  }

  return response;
}

Custom Auth Base URL

typescript
// For development or custom deployments
const auth = new BkperAuth({
  baseUrl: 'https://custom-auth.example.com',
  // ... callbacks
});

Design System (@bkper/web-design)

Installation

bash
bun add @bkper/web-design

Usage in CSS

css
@import '@bkper/web-design';

.my-component {
  color: var(--bkper-font-color-default);
  font-size: var(--bkper-font-size-medium);
  padding: var(--bkper-spacing-medium);
  border: var(--bkper-border);
  border-radius: var(--bkper-border-radius);
}

Usage via CDN

html
<link rel="stylesheet" href="https://bkper.app/design/v2/style.css">

Design Tokens

Typography

css
--bkper-font-size-x-small   /* 0.75rem */
--bkper-font-size-small     /* 0.85rem */
--bkper-font-size-medium    /* 1rem */
--bkper-font-size-large     /* 1.25rem */
--bkper-font-color-default
--bkper-font-weight-bold
--bkper-line-height-normal  /* 1.8 */

Spacing

css
--bkper-spacing-3x-small    /* 0.125rem */
--bkper-spacing-2x-small    /* 0.25rem */
--bkper-spacing-x-small     /* 0.5rem */
--bkper-spacing-small       /* 0.75rem */
--bkper-spacing-medium      /* 1rem */
--bkper-spacing-large       /* 1.5rem */
--bkper-spacing-x-large     /* 2rem */

Colors

css
/* Semantic colors */
--bkper-color-primary
--bkper-color-success
--bkper-color-danger
--bkper-color-warning
--bkper-color-neutral

/* Account type colors (light theme) */
--bkper-color-blue-high     /* Assets */
--bkper-color-yellow-high   /* Liabilities */
--bkper-color-green-high    /* Incoming */
--bkper-color-red-high      /* Outgoing */

/* Each color has low/medium/high variants */
--bkper-color-blue-low
--bkper-color-blue-medium
--bkper-color-blue-high

Borders

css
--bkper-border              /* 1px solid border-color */
--bkper-border-color
--bkper-border-radius       /* 0.375rem */

Theming (Light/Dark)

html
<!-- Light theme (default) -->
<body class="wa-light">

<!-- Dark theme -->
<body class="wa-dark">

Colors automatically adjust for dark theme.

Lit Components

Basic Component Structure

typescript
import { LitElement, html, css } from 'lit';
import { customElement, property, state } from 'lit/decorators.js';
import '@bkper/web-design';

@customElement('my-component')
export class MyComponent extends LitElement {
  static styles = css`
    :host {
      display: block;
      padding: var(--bkper-spacing-medium);
    }
    
    .title {
      font-size: var(--bkper-font-size-large);
      font-weight: var(--bkper-font-weight-bold);
    }
    
    .amount-positive {
      color: var(--bkper-color-green-high);
    }
    
    .amount-negative {
      color: var(--bkper-color-red-high);
    }
  `;

  @property({ type: String })
  bookId = '';

  @state()
  private loading = false;

  render() {
    return html`
      <div class="title">My Component</div>
      ${this.loading 
        ? html`<div>Loading...</div>` 
        : html`<slot></slot>`
      }
    `;
  }
}

Using with BkperAuth

typescript
import { LitElement, html } from 'lit';
import { customElement, state } from 'lit/decorators.js';
import { BkperAuth } from '@bkper/web-auth';

@customElement('my-app')
export class MyApp extends LitElement {
  private auth = new BkperAuth({
    onLoginSuccess: () => this.authenticated = true,
    onLoginRequired: () => this.authenticated = false,
  });

  @state()
  private authenticated = false;

  async connectedCallback() {
    super.connectedCallback();
    await this.auth.init();
  }

  render() {
    if (!this.authenticated) {
      return html`
        <button @click=${() => this.auth.login()}>
          Login with Bkper
        </button>
      `;
    }

    return html`
      <my-dashboard></my-dashboard>
      <button @click=${() => this.auth.logout()}>Logout</button>
    `;
  }
}

Hono Web Server

Basic Setup

typescript
import { Hono } from 'hono';
import { serveStatic } from 'hono/cloudflare-workers';

type Env = {
  BKPER_API_KEY: string;
};

const app = new Hono<{ Bindings: Env }>();

// Serve static client assets
app.use('/*', serveStatic({ root: './' }));

// API routes
app.get('/api/health', (c) => c.json({ status: 'ok' }));

app.get('/api/book/:bookId', async (c) => {
  const { bookId } = c.req.param();
  // Fetch book data...
  return c.json({ bookId });
});

export default app;

Menu Integration Route

typescript
// Handle menu popup opening
app.get('/', async (c) => {
  const bookId = c.req.query('bookId');
  
  // Return HTML that loads your Lit app
  return c.html(`
    <!DOCTYPE html>
    <html>
      <head>
        <link rel="stylesheet" href="https://bkper.app/design/v2/style.css">
        <script type="module" src="/index.js"></script>
      </head>
      <body class="wa-light">
        <my-app book-id="${bookId}"></my-app>
      </body>
    </html>
  `);
});

Project Structure

code
packages/web/
├── client/                 # Lit components (browser)
│   ├── src/
│   │   ├── index.html
│   │   ├── index.ts
│   │   └── components/
│   │       └── my-component.ts
│   ├── package.json
│   └── vite.config.ts
│
└── server/                 # Hono worker (serves client + API)
    ├── src/
    │   ├── index.ts
    │   └── routes/
    │       └── api.ts
    ├── package.json
    └── wrangler.jsonc

Development Workflow

bash
# Start development server
bun run dev

# Build for production
bun run build

# Deploy to Bkper Platform
bkper app deploy