Skip to content

Latest commit

 

History

History
213 lines (157 loc) · 6.64 KB

File metadata and controls

213 lines (157 loc) · 6.64 KB

Customization Guide

This document shows the specific files and code changes to make for each common customization.


1. Replace brand (logo, colors, app name)

Logo — drop your SVG into frontend/public/logo.svg. The <img src="/logo.svg" /> references in Login.tsx, Dashboard.tsx, and Admin.tsx will pick it up automatically.

Colors — edit the CSS variables at the top of frontend/src/styles.css:

/* Before */
:root {
  --color-primary: #6366f1;       /* indigo */
  --color-primary-hover: #4f46e5;
}

/* After — e.g. ClinicIQ teal */
:root {
  --color-primary: #0d9488;       /* teal-600 */
  --color-primary-hover: #0f766e; /* teal-700 */
}

App name — frontend/index.html line 7:

<!-- Before -->
<title>My App — Powered by MIOSA</title>

<!-- After -->
<title>ClinicIQ Workspace</title>

2. Swap auth provider

Auth0

  1. Install python-jose[cryptography] in pyproject.toml.
  2. Replace backend/app/routes/auth.py with an Auth0 token verifier:
# backend/app/routes/auth.py — Auth0 variant
import httpx
from fastapi import HTTPException

AUTH0_DOMAIN = "your-tenant.auth0.com"
AUTH0_AUDIENCE = "https://api.yourapp.com"

async def verify_auth0_token(token: str) -> dict:
    jwks_uri = f"https://{AUTH0_DOMAIN}/.well-known/jwks.json"
    async with httpx.AsyncClient() as client:
        jwks = (await client.get(jwks_uri)).json()
    # Validate token against JWKS — use python-jose
    from jose import jwt as jose_jwt
    payload = jose_jwt.decode(token, jwks, algorithms=["RS256"], audience=AUTH0_AUDIENCE)
    return payload
  1. In deps.py, replace jwt.decode(...) with your verify_auth0_token() call and look up the user by payload["sub"] (the Auth0 user ID).
  2. On the frontend, replace Login.tsx with Auth0's LoginButton and useAuth0() from @auth0/auth0-react. Store the Auth0 access token in place of the local JWT.

Supabase Auth

// frontend/src/auth.tsx — Supabase variant
import { createClient } from "@supabase/supabase-js";

const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);

export function useAuth() {
  // Use supabase.auth.getSession() and supabase.auth.signInWithPassword()
  // Pass supabase's JWT to your FastAPI backend as Bearer token
}

On the backend, verify with Supabase's JWKS endpoint (same pattern as Auth0 above).

Clerk

Use Clerk's @clerk/clerk-react on the frontend. Pass the session token to FastAPI. Verify with Clerk's JWT public key in deps.py.


3. Change component layout (Studio)

The Studio page uses CSS Grid: 240px sidebar | 1fr main | 320px terminal.

To make the terminal full-width at the bottom:

// frontend/src/pages/Studio.tsx
// Change the layout div:

// Before
<div className="studio-layout">  {/* grid: sidebar | main | terminal */}

// After — terminal below
<div style={{ display: "flex", flexDirection: "column", flex: 1, overflow: "hidden" }}>
  <div style={{ display: "grid", gridTemplateColumns: "240px 1fr", flex: 1, overflow: "hidden" }}>
    <aside><MiosaFileTree sandboxId={sandboxId} token={tok.token} /></aside>
    <main><MiosaPreview sandboxId={sandboxId} token={tok.token} /></main>
  </div>
  <div style={{ height: "280px", borderTop: "1px solid var(--color-border)" }}>
    <MiosaTerminal sandboxId={sandboxId} token={tok.token} />
  </div>
</div>

4. Add MIOSA databases to the Studio

// frontend/src/pages/Studio.tsx — add MiosaDatabase
import { MiosaDatabase, MiosaFileTree, MiosaPreview, MiosaTerminal } from "@miosa/react";

// Inside the layout:
<aside><MiosaDatabase sandboxId={sandboxId} token={tok.token} /></aside>

On the backend, expose a /api/sandboxes/{id}/database-token endpoint analogous to tokens.py but calling miosa.databases.preview_token(sandbox_id).


5. Swap Vite for Next.js (App Router)

  1. Replace frontend/ with a Next.js 15 app: npx create-next-app@latest frontend.
  2. Move api.ts and auth.tsx to frontend/src/lib/.
  3. Convert pages to the App Router layout:
    • pages/Dashboard.tsx → frontend/src/app/page.tsx (Server Component fetches sandbox list)
    • pages/Studio.tsx → frontend/src/app/studio/[id]/page.tsx
  4. Studio.tsx uses MiosaPreview (a client component), so add "use client" at the top.
  5. For the preview token, fetch it in a Server Component and pass as a prop to the client component — this keeps the backend call on the server, not in the browser.
// app/studio/[id]/page.tsx (Server Component)
import { cookies } from "next/headers";
import { StudioClient } from "./StudioClient";

export default async function StudioPage({ params }: { params: { id: string } }) {
  const token = cookies().get("token")?.value;
  const res = await fetch(`${process.env.BACKEND_URL}/api/sandboxes/${params.id}/preview-token`, {
    method: "POST",
    headers: { Authorization: `Bearer ${token}` },
  });
  const { token: previewToken } = await res.json();
  return <StudioClient sandboxId={params.id} previewToken={previewToken} />;
}

6. Replace FastAPI with Flask or Django

The key contract is: any framework that can:

  • Verify a JWT (PyJWT works in all three)
  • Make async HTTP calls to MIOSA (httpx works in all three)
  • Return JSON

Flask variant for tokens.py:

from flask import Blueprint, jsonify, abort, g
import miosa, jwt

bp = Blueprint("tokens", __name__)
client = miosa.Miosa(api_key=MIOSA_API_KEY)

@bp.post("/api/sandboxes/<sandbox_id>/preview-token")
def mint_preview_token(sandbox_id):
    sandbox = client.sandboxes.get(sandbox_id)  # sync client
    if sandbox.external_user_id != g.user.id:
        abort(403)
    token = client.sandboxes.preview_token(sandbox_id, expires_in=3600)
    return jsonify({"token": token.value, "expires_at": token.expires_at.isoformat()})

Django variant:

Use miosa.Miosa(api_key=...) in a Django view. The external_user_id pattern and ownership check are identical — only the request/response API differs.


7. Enable multi-tenant subdomain routing

If you want alice.yourapp.com and bob.yourapp.com as isolated tenants:

  1. In FastAPI, read the Host header to resolve the tenant.
  2. Store a tenant_id → miosa_api_key map in your database.
  3. Instantiate a per-request MIOSA client with the resolved key:
# deps.py — per-tenant client
async def get_miosa_client(request: Request, db = Depends(get_db)) -> miosa.Miosa:
    host = request.headers.get("host", "")
    subdomain = host.split(".")[0]
    tenant = await db.execute(select(Tenant).where(Tenant.subdomain == subdomain))
    if not tenant:
        raise HTTPException(404, "Unknown tenant")
    return miosa.Miosa(api_key=tenant.miosa_api_key)