commit 3527e732d4438c9df6a60d0555a8b2b2890f8e31 Author: Alejandro Gutiérrez <35082514+alezmad@users.noreply.github.com> Date: Mon Feb 2 17:29:12 2026 +0000 feat: turbostarter boilerplate Production-ready Next.js boilerplate with: - Runtime env validation (fail-fast on missing vars) - Feature-gated config (S3, Stripe, email, OAuth) - Docker + Coolify deployment pipeline - PostgreSQL + pgvector, MinIO S3, Better Auth - TypeScript strict mode (no ignoreBuildErrors) - i18n (en/es), AI modules, billing, monitoring Co-Authored-By: Claude Opus 4.5 diff --git a/.context/ARCHITECTURE.md b/.context/ARCHITECTURE.md new file mode 100644 index 0000000..676f98e --- /dev/null +++ b/.context/ARCHITECTURE.md @@ -0,0 +1,329 @@ +# TurboStarter Architecture Reference + +> LLM context document. Token-optimized. Source of truth: code in `packages/` and `apps/`. + +## Stack Summary + +``` +Apps: web (Next.js 16, React 19) | mobile (Expo 54, React Native) +API: Hono (packages/api) → /api/* routes +Auth: Better Auth 1.4.6 (packages/auth) +DB: PostgreSQL + Drizzle ORM + pgvector (packages/db) +Billing: Stripe | LemonSqueezy | Polar (packages/billing) +AI: Multi-provider via AI SDK (packages/ai) +UI: shadcn/ui + Radix (packages/ui-web, packages/ui-mobile) +``` + +## Directory Map + +``` +apps/ + web/src/ + app/[locale]/ # Next.js App Router (i18n) + (marketing)/ # Public: landing, blog, pricing + auth/ # Login, register, password reset + dashboard/ + (user)/ # Personal dashboard + [organization]/ # Multi-tenant org routes + admin/ # Super admin + modules/ # Feature modules + lib/api/ # API client (server.ts, client.tsx) + config/ # paths.ts, app.ts + mobile/src/ + app/ # Expo Router + modules/ # Feature modules + +packages/ + api/src/ + index.ts # Main router, exports AppRouter type + modules/ # Feature routers (admin/, ai/, auth/, billing/, organizations/, storage/) + auth/src/ + server.ts # Better Auth config + client.tsx # Client helpers + db/src/ + schema/ # Drizzle schemas (auth.ts, chat.ts, image.ts, pdf.ts, customer.ts, credit-transaction.ts) + migrations/ # SQL migration files + ai/src/modules/ + chat/ # Multi-provider chat + image/ # Image generation + pdf/ # RAG pipeline + tts/ # Text-to-speech + stt/ # Speech-to-text + credits/ # Usage metering + billing/src/ + providers/ # stripe/, lemonsqueezy/, polar/ + i18n/translations/ # JSON translation files + ui/web/src/ # 45+ shadcn components + ui/mobile/src/ # React Native components +``` + +## Database Schema + +### Tables by Schema + +| Schema | Table | Key Columns | Purpose | +|--------|-------|-------------|---------| +| public | `user` | id, email, emailVerified, banned, role, isAnonymous | Users | +| public | `session` | id, userId, activeOrganizationId, impersonatedBy | Sessions | +| public | `account` | userId, providerId, accountId | OAuth accounts | +| public | `verification` | identifier, value, expiresAt | Email tokens | +| public | `passkey` | userId, publicKey, credentialId | WebAuthn | +| public | `two_factor` | userId, secret, backupCodes | 2FA | +| public | `organization` | id, name, slug | Orgs | +| public | `member` | userId, organizationId, role | Membership | +| public | `invitation` | email, organizationId, role, status | Invites | +| public | `customer` | userId, customerId, plan, credits | Billing | +| public | `credit_transaction` | customerId, type, amount, balance | Credit ledger | +| chat | `chat` | id, userId, title | Chat sessions | +| chat | `message` | chatId, role, content | Messages | +| chat | `part` | messageId, type, order, details | Message parts | +| image | `generation` | userId, prompt, model, aspectRatio | Image requests | +| image | `image` | generationId, url | Generated images | +| pdf | `document` | userId, name, status, s3Key | PDF files | +| pdf | `chat` | documentId, userId | PDF chats | +| pdf | `message` | chatId, role, content | PDF messages | +| pdf | `embedding` | documentId, chunkIndex, embedding(1536) | Vectors | +| pdf | `retrieval_chunk` | documentId, content, pageNumber | Semantic chunks | +| pdf | `citation_unit` | documentId, pageNumber, bbox, content | Citations | + +### Enums + +```typescript +// packages/db/src/schema/auth.ts +memberRole: 'owner' | 'admin' | 'member' +invitationStatus: 'pending' | 'accepted' | 'rejected' | 'canceled' + +// packages/db/src/schema/customer.ts +billingPlan: 'free' | 'premium' | 'enterprise' + +// packages/db/src/schema/credit-transaction.ts +creditTransactionType: 'signup' | 'purchase' | 'usage' | 'admin_grant' | 'admin_deduct' | 'refund' | 'promo' | 'referral' | 'expiry' + +// packages/db/src/schema/chat.ts +messageRole: 'system' | 'assistant' | 'user' + +// packages/db/src/schema/image.ts +aspectRatio: '1:1' | '16:9' | '9:16' | '4:3' | '3:4' + +// packages/db/src/schema/pdf.ts +documentStatus: 'pending' | 'processing' | 'ready' | 'failed' +citationUnitType: 'prose' | 'heading' | 'list' | 'table' | 'code' +``` + +## API Pattern + +### Router Structure + +```typescript +// packages/api/src/index.ts +app.route("/admin", adminRouter) // Admin operations +app.route("/ai", aiRouter) // AI features +app.route("/auth", authRouter) // Auth (Better Auth) +app.route("/billing", billingRouter)// Billing webhooks/checkout +app.route("/organizations", orgRouter) +app.route("/storage", storageRouter) +``` + +### Module Pattern + +``` +packages/api/src/modules// + router.ts # Hono router + queries.ts # Read operations + mutations.ts # Write operations +``` + +### Type-Safe Client + +```typescript +// Server component +import { api } from "~/lib/api/server"; +const data = await api.admin.users.$get({ query: { page: "1" } }); + +// Client component +import { api } from "~/lib/api/client"; +const { data } = useQuery({ queryKey: ["users"], queryFn: () => api.admin.users.$get() }); +``` + +## AI Providers + +### Chat Models + +| Provider | Models | Package | +|----------|--------|---------| +| OpenAI | gpt-5.1, gpt-4o, o3, o4-mini | @ai-sdk/openai | +| Anthropic | claude-4-sonnet, claude-3.7-sonnet | @ai-sdk/anthropic | +| Google | gemini-2.5-pro, gemini-2.5-flash | @ai-sdk/google | +| xAI | grok-4, grok-3-mini-fast | @ai-sdk/xai | +| DeepSeek | deepseek-v3, deepseek-r1 | @ai-sdk/deepseek | + +### Image Models + +| Provider | Models | +|----------|--------| +| OpenAI | gpt-image-1, dall-e-2, dall-e-3 | +| Replicate | recraft-v3, photon, stable-diffusion-3.5 | + +### Other AI Services + +| Service | Provider | Location | +|---------|----------|----------| +| TTS | ElevenLabs | packages/ai/src/modules/tts/ | +| STT | OpenAI Whisper | packages/ai/src/modules/stt/ | +| Embeddings | OpenAI | packages/ai/src/modules/pdf/ | +| Vector Search | pgvector + HNSW | packages/db/src/schema/pdf.ts | + +## Auth Configuration + +### Methods + +``` +Email/Password | Magic Link | OAuth (Google, GitHub, Apple) | Passkeys | 2FA/TOTP | Anonymous +``` + +### Session Fields + +```typescript +session: { + userId: string + activeOrganizationId?: string // Multi-tenant context + impersonatedBy?: string // Admin impersonation +} +``` + +### RBAC + +``` +Organization roles: owner > admin > member +User role field: 'user' | 'admin' (super admin) +``` + +## Billing Configuration + +### Providers + +```typescript +// packages/billing/src/providers/ +stripe/ # Stripe subscriptions + one-time +lemonsqueezy/ # LemonSqueezy +polar/ # Polar +``` + +### Credit System + +```typescript +// Deduct credits +await deductCredits(customerId, amount, 'usage', { feature: 'chat' }); + +// Check balance +const balance = await getCreditsBalance(customerId); + +// Transaction types +'signup' | 'purchase' | 'usage' | 'admin_grant' | 'admin_deduct' | 'refund' | 'promo' | 'referral' | 'expiry' +``` + +## Commands + +```bash +# Dev +pnpm install # Install deps +pnpm services:start # Start PostgreSQL (Docker) +pnpm with-env -F @turbostarter/db db:setup # Migrate + seed +pnpm dev # Start all apps +pnpm --filter web dev # Web only +pnpm --filter mobile ios # Mobile iOS + +# Database +pnpm with-env -F @turbostarter/db db:generate # Generate migration +pnpm with-env -F @turbostarter/db db:migrate # Apply migrations +pnpm with-env -F @turbostarter/db db:push # Push (dev only) +pnpm with-env -F @turbostarter/db db:studio # GUI + +# Quality +pnpm typecheck # Type check all +pnpm lint # ESLint +pnpm test # Vitest +pnpm build # Build all +``` + +## Critical Invariants + +### Must Do + +- Use `pnpm with-env` for all DB commands +- Go through API layer for data access +- Server-side auth/authz enforcement +- Use Drizzle ORM, never raw SQL (except migrations) +- Use existing UI components from packages/ui-* + +### Must Not + +- Access DB directly from apps +- Client-side auth checks as security +- Business logic in React components +- Skip migrations in production +- Introduce new state management libs + +## File Patterns + +### Add Dashboard Page + +``` +1. Define path: apps/web/src/config/paths.ts +2. Add sidebar item: apps/web/src/app/[locale]/dashboard/(user)/layout.tsx +3. Create page: apps/web/src/app/[locale]/dashboard/(user)/my-feature/page.tsx +4. Add translations: packages/i18n/translations/en/dashboard.json +``` + +### Add API Endpoint + +``` +1. Create module: packages/api/src/modules// +2. Add router.ts, queries.ts, mutations.ts +3. Mount in packages/api/src/index.ts +4. Types auto-available via Hono RPC +``` + +### Add DB Table + +``` +1. Edit schema: packages/db/src/schema/.ts +2. Export from packages/db/src/schema/index.ts +3. Generate: pnpm with-env -F @turbostarter/db db:generate +4. Migrate: pnpm with-env -F @turbostarter/db db:migrate +``` + +## Package Exports + +| Package | Export | Use | +|---------|--------|-----| +| @turbostarter/db | /server | Server-only DB access | +| @turbostarter/auth | /server, /client | Auth helpers | +| @turbostarter/api | /utils | handle(), response helpers | +| @turbostarter/i18n | /server, /client | Translation functions | +| @turbostarter/ui-web | / | UI components | + +## Environment Variables + +### Required (turbo.json globalEnv) + +``` +DATABASE_URL # PostgreSQL connection +PRODUCT_NAME # App name +URL # Base URL +DEFAULT_LOCALE # Default language (en) +``` + +### Location + +``` +.env # Root (DB, shared secrets) +apps/web/.env.local # Web-specific +apps/mobile/.env.local # Mobile-specific +``` + +## Not In This Codebase + +- Browser extension (apps/extension) - available in TurboStarter Core separately +- WXT framework references +- Extension-specific docs in .context/turbostarter-framework-context/sections/extension/ diff --git a/.context/turbostarter-framework-context/CLAUDE.md b/.context/turbostarter-framework-context/CLAUDE.md new file mode 100644 index 0000000..58478a1 --- /dev/null +++ b/.context/turbostarter-framework-context/CLAUDE.md @@ -0,0 +1,41 @@ +# TurboStarter Framework Context + +TurboStarter framework documentation for AI context loading. + +## When to Read More + +**Read `index.md`** if you need to: +- Find TurboStarter documentation on a specific topic +- Search by keyword (auth, database, billing, api, etc.) +- Understand what documentation is available + +**Read `framework.md`** for: +- pnpm commands and workflows +- Monorepo structure +- Code conventions + +## Quick Reference + +| Need | Read | +|------|------| +| Commands & patterns | `framework.md` | +| Authentication | `sections/web/auth/` | +| Database/Drizzle | `sections/web/database/` | +| API/Hono | `sections/web/api/` | +| Billing/Stripe | `sections/web/billing/` | +| UI Components | `sections/web/ui/` | +| Organizations | `sections/web/organizations/` | +| i18n | `sections/web/i18n/` | +| Mobile | `sections/mobile/` | + +## Refreshing + +```bash +python .context/turbostarter-framework-context/refresh-docs.py +``` + +## Notes + +- These docs are **subordinate** to `.context/CLAUDE.md` +- Adapt patterns to match existing codebase, don't copy verbatim +- When in doubt, check the actual code in `packages/` and `apps/` diff --git a/.context/turbostarter-framework-context/framework.md b/.context/turbostarter-framework-context/framework.md new file mode 100644 index 0000000..4d66278 --- /dev/null +++ b/.context/turbostarter-framework-context/framework.md @@ -0,0 +1,992 @@ +# TurboStarter Framework Patterns + +TurboStarter framework patterns and commands. + +> **Note:** This file is **subordinate to `.context/CLAUDE.md`**. Project-specific decisions take precedence. +> +> **For more docs:** Check `index.md` for keyword search across 222 documentation pages. + +## Purpose + +This document contains TurboStarter monorepo patterns, commands, and architecture guidelines: +- Framework-specific commands (pnpm, database, services) +- Monorepo structure and package organization +- Code conventions and patterns established by TurboStarter + +**When to consult `.context/project.md` instead:** +- Project-specific architecture decisions +- Project package structure +- Business logic and feature requirements + +**Documentation usage**: Examples here are illustrative. Adapt them to match existing repository patterns rather than copying verbatim. + +## Project Overview + +This is a TurboStarter monorepo - a fullstack SaaS starter kit built with Turborepo. It contains: +- **Web app**: Next.js 16 (App Router) with React 19 +- **Mobile app**: React Native + Expo +- **Shared packages**: API (Hono), auth (Better Auth), database (Drizzle + PostgreSQL), billing, email, i18n, UI components + +## Essential Commands + +### Development +```bash +# Install dependencies (uses pnpm 10.25.0) +pnpm install + +# Start Docker services (PostgreSQL) +pnpm services:start + +# First-time database setup (migrate + seed) +pnpm with-env -F @turbostarter/db db:setup + +# Start all apps in dev mode +pnpm dev + +# Start specific app only +pnpm --filter web dev +pnpm --filter mobile dev + +# Mobile-specific dev commands +pnpm --filter mobile ios # Run on iOS simulator +pnpm --filter mobile android # Run on Android emulator +``` + +### Database Operations +All database commands must use `pnpm with-env` to load environment variables from `.env` at repo root. + +```bash +# Generate migration after schema changes +pnpm with-env -F @turbostarter/db db:generate + +# Apply migrations +pnpm with-env -F @turbostarter/db db:migrate + +# Push schema directly (dev only - skips migrations) +pnpm with-env -F @turbostarter/db db:push + +# Check schema drift +pnpm with-env -F @turbostarter/db db:check + +# Open Drizzle Studio (database GUI) +pnpm with-env -F @turbostarter/db db:studio + +# Check migration status +pnpm with-env -F @turbostarter/db db:status + +# Seed database (dev only) +pnpm with-env -F @turbostarter/db db:seed + +# Reset database (dev only) +pnpm with-env -F @turbostarter/db db:reset +``` + +### Quality & Testing +```bash +# Type check entire monorepo +pnpm typecheck + +# Lint (check) +pnpm lint + +# Lint (fix) +pnpm lint:fix + +# Format (check) +pnpm format + +# Format (fix) +pnpm format:fix + +# Run tests (Vitest) +pnpm test + +# Run tests in watch mode +pnpm test:projects:watch + +# Build all packages/apps +pnpm build + +# Build specific app +pnpm --filter web build +``` + +### Services +```bash +# Start Docker services +pnpm services:start + +# Stop Docker services +pnpm services:stop + +# View service logs +pnpm services:logs + +# Check service status +pnpm services:status +``` + +## Architecture + +### Monorepo Structure +- `apps/web/` - Next.js web application +- `apps/mobile/` - React Native (Expo) mobile app +- `packages/api/` - Hono API server with modular routers +- `packages/auth/` - Better Auth configuration and helpers +- `packages/billing/` - Billing integrations (Stripe, LemonSqueezy) +- `packages/db/` - Drizzle ORM schema, migrations, and database utilities +- `packages/email/` - Email templates and providers +- `packages/i18n/` - Internationalization setup and translations +- `packages/shared/` - Common utilities, hooks, constants +- `packages/storage/` - File storage providers and types +- `packages/ui/` - Shared UI components (web/mobile variants) + +### API Architecture (Hono) +The API is built with Hono and follows a modular router pattern: + +**Main router** (`packages/api/src/index.ts`): +- Base path: `/api` +- Applies middleware: CSRF (web only), CORS, logger, localization +- Routes to sub-routers: `/admin`, `/ai`, `/auth`, `/billing`, `/organizations`, `/storage` + +**Module pattern**: +Each feature module (e.g., `packages/api/src/modules/admin/users/`) contains: +- `router.ts` - Hono router with route definitions +- `queries.ts` - Database query functions +- `mutations.ts` - Database mutation functions +- Schema validation via Zod + +**Type safety**: +- API types are exported from `packages/api/src/index.ts` as `AppRouter` +- Consumed in web/mobile apps via Hono RPC client for end-to-end type safety + +### Web App Structure (Next.js App Router) +``` +apps/web/src/app/ +├── [locale]/ # Internationalized routes +│ ├── (marketing)/ # Public routes (landing, blog, pricing) +│ ├── auth/ # Auth pages (login, register, password reset) +│ ├── dashboard/ # Protected routes +│ │ ├── (user)/ # Personal dashboard +│ │ └── [organization]/ # Organization-scoped routes +│ └── admin/ # Super admin dashboard +└── api/[...route]/route.ts # Catch-all API route (proxies to Hono) +``` + +**Route groups**: +- `(marketing)` - Public pages, shared marketing layout +- `(user)` - Personal user dashboard +- `[organization]` - Multi-tenant org routes, slug-based + +**API integration**: +- Server components: Use `api` from `~/lib/api/server.ts` +- Client components: Use `api` from `~/lib/api/client.tsx` with React Query + +### Database (Drizzle + PostgreSQL) +**Schema location**: `packages/db/src/schema/` +- Multiple schema files organized by domain +- Exported via `packages/db/src/schema/index.ts` + +**Migrations**: +- Generated in `packages/db/migrations/` as SQL files +- Workflow: Edit schema → `db:generate` → `db:migrate` + +**Database client**: +- Server-side only via `@turbostarter/db/server` +- Uses Drizzle ORM with PostgreSQL driver +- Connection pooling configured for serverless + +**Critical invariants**: +- ❌ **Never** access database directly from web/mobile apps +- ❌ **Never** use raw SQL outside migrations (use Drizzle queries) +- ❌ **Never** skip migrations in production (only `db:push` for local dev) +- ✅ **Always** use `pnpm with-env` for all database commands +- ✅ **Always** go through API layer for data access + +### Authentication (Better Auth) +- Server config: `packages/auth/src/server.ts` +- Client helpers: `packages/auth/src/client.tsx` +- Supports: email/password, magic links, OAuth providers, 2FA, passkeys +- Session management via cookies +- Organizations plugin enabled for multi-tenancy + +### Multi-tenancy / Organizations +- Organization-scoped routes: `/dashboard/[organization]/...` +- Active organization stored in session (`activeOrganizationId`) +- RBAC: owner, admin, member roles +- Invitation system with email-based invites + +### Business Logic Placement + +**Core principle**: Business logic lives in the API layer, not in UI components. + +**Where logic belongs**: +- **API layer** (`packages/api/src/modules/`): Business rules, validation, authorization, data transformations +- **Database layer** (`packages/db`): Schema definitions, relations, type-safe queries via Drizzle +- **Web/Mobile apps**: Orchestration, presentation, user interaction, calling API endpoints +- **UI packages**: Pure presentation components, no business rules + +**Where logic must NOT live**: +- React components (web or mobile) +- UI packages (`@turbostarter/ui-web`, `@turbostarter/ui-mobile`) +- Directly in API route files (use `queries.ts`/`mutations.ts` instead) +- Client-side validation as source of truth (use for UX only; validate server-side) + +**Example**: +```tsx +// ❌ BAD - business logic in component +export function UserProfile({ userId }) { + const canEdit = user.role === 'admin' || user.id === userId; + // Complex business rules in component +} + +// ✅ GOOD - business logic in API +export function UserProfile({ userId }) { + const { data } = useQuery(api.users.canEdit.$get({ query: { userId } })); + // API returns authorization decision +} +``` + +### Layout & Sidebar Patterns + +**Dashboard Layout Hierarchy**: +``` +layout.tsx (root) +└── [locale]/layout.tsx (i18n wrapper) + └── dashboard/layout.tsx (main dashboard - no sidebar) + ├── (user)/layout.tsx (user sidebar + auth check) + │ ├── page.tsx (home) + │ ├── ai/page.tsx + │ └── settings/layout.tsx (sub-nav) + │ ├── page.tsx (general) + │ ├── security/page.tsx + │ └── billing/page.tsx + ├── [organization]/layout.tsx (org sidebar + auth check + org fetch) + │ ├── page.tsx (org home) + │ ├── members/page.tsx + │ └── settings/layout.tsx (sub-nav) + └── admin/layout.tsx (admin sidebar + permission check) + ├── page.tsx (admin home) + ├── users/page.tsx + └── organizations/page.tsx +``` + +**Sidebar Structure**: +Each layout defines its own sidebar menu with groups: +- **User sidebar**: Personal features (home, AI) + account (settings) +- **Organization sidebar**: Platform features (home) + organization (settings, members) +- **Admin sidebar**: Admin resources (users, organizations, customers) + +**Common sidebar footer** (all sidebars): +- Support link +- Feedback link +- User navigation (profile, logout) + +**Layout Authentication Patterns**: +```tsx +// User dashboard - basic auth +const { user } = await getSession(); +if (!user) return redirect(pathsConfig.auth.login); + +// Organization dashboard - auth + org fetch + hydration +const { user } = await getSession(); +if (!user) return redirect(pathsConfig.auth.login); +const org = await getOrganization({ slug }); +if (!org) return redirect(pathsConfig.dashboard.user.index); +// Pre-fetch and hydrate organization data via queryClient + +// Admin dashboard - auth + permission check +const { user } = await getSession(); +if (!user) return redirect(pathsConfig.auth.login); +if (!hasAdminPermission(user)) return redirect(pathsConfig.dashboard.user.index); +``` + +## Environment Variables + +**Required globals** (defined in `turbo.json`): +- `DATABASE_URL` - PostgreSQL connection string +- `PRODUCT_NAME` - Application name +- `URL` - Base URL for web app +- `DEFAULT_LOCALE` - Default language (e.g., "en") + +**Setup**: +1. Create `.env` at repo root +2. Copy from `.env.example` files +3. Commands automatically load via `pnpm with-env` + +**App-specific variables**: +- Web: `apps/web/.env.local` +- Mobile: `apps/mobile/.env.local` + +## Common UI Patterns + +### Dashboard Components +Standard dashboard components from `~/modules/common/layout/dashboard/`: +- `DashboardHeader` - Page header container +- `DashboardHeaderTitle` - Main page title (h1) +- `DashboardHeaderDescription` - Subtitle/description text +- `DashboardInset` - Main content wrapper with proper spacing +- `DashboardSidebar` - Collapsible sidebar with menu +- `SidebarLink` - Navigation link with active state + +### Data Tables +For admin/list pages, use the data table pattern: +- `DataTableSkeleton` - Loading skeleton during Suspense +- `createSearchParamsCache` from `nuqs/server` - Type-safe URL params +- `handle()` from `@turbostarter/api/utils` - Unwraps API responses +- React Query for client-side data fetching +- Built-in sorting, filtering, pagination via URL params + +### Icons +Import from `@turbostarter/ui-web/icons`: +```tsx +import { Icons } from "@turbostarter/ui-web/icons"; + + + + +``` + +Common icons: +- `Home`, `Settings`, `UsersRound`, `Building` (sidebar) +- `Brain` (AI), `HandCoins` (billing), `LifeBuoy` (support) +- `MessageCircle` (feedback) + +### Internationalization (i18n) +- Translation keys: `namespace:key.nested.path` +- Server: `const { t } = await getTranslation({ ns: "dashboard" })` +- Client: `const { t } = useTranslation({ ns: "dashboard" })` +- Sidebar labels auto-translate if key exists in `common.json` +- Metadata: `getMetadata({ title: "common:myFeature" })` + +### UI Components (shadcn/ui) +TurboStarter uses [shadcn/ui](https://ui.shadcn.com) for atomic, accessible, customizable components built with Tailwind CSS and Radix UI. + +**Two UI packages**: +- `@turbostarter/ui` - Shared styles, themes, assets (icons) +- `@turbostarter/ui-web` - Pre-built web components (Button, Card, Dialog, etc.) + +**Adding new components**: +```bash +# From repo root - launches interactive CLI +pnpm --filter @turbostarter/ui-web ui:add + +# Or copy-paste from shadcn/ui website into packages/ui/web/src/ +``` + +**Using components** (each has standalone export): +```tsx +// Import from specific component path +import { Card, CardContent, CardHeader } from "@turbostarter/ui-web/card"; +import { Button } from "@turbostarter/ui-web/button"; +import { Dialog, DialogContent } from "@turbostarter/ui-web/dialog"; + +// Build app-specific components by composition +export function MyComponent() { + return ( + + ... + ... + + ); +} +``` + +**Component organization principle**: +- **Shared package** (`@turbostarter/ui-web`): Atomic components (Button, Input, Card, Dialog) +- **App directory** (`apps/web/src/`): Specific composed components (LoginForm, UserProfile) +- Keep shared components atomic for reusability and tree-shaking + +## Security Boundaries + +**Critical principle**: Authentication and authorization are **server-side only**. Client-side checks are for UX, never security. + +**Security rules**: +- ✅ **Auth/authz enforcement**: API layer only (via Better Auth + middleware) +- ✅ **Secrets/env vars**: Server-side packages only (`packages/api`, `packages/db`) +- ✅ **Role checks**: API layer, never client components +- ✅ **Organization permissions**: Validated in API, pre-fetched for UX + +**Never do this**: +- ❌ Client-side role/permission checks as source of truth +- ❌ Secrets in web/mobile apps or UI packages +- ❌ Authorization logic in React components +- ❌ Direct API calls bypassing authentication + +**Pattern**: +```tsx +// ❌ BAD - client-side auth check +export function AdminPanel() { + if (user.role !== 'admin') return null; // Security by obscurity! + return ; +} + +// ✅ GOOD - server-side enforcement +export async function AdminPanel() { + const { user } = await getSession(); + if (!hasAdminPermission(user)) redirect(pathsConfig.dashboard.user.index); + const data = await api.admin.getSensitiveData(); // API enforces auth + return ; +} +``` + +## Architectural Constraints + +**Allowed patterns** (use these freely): +- Hono for API routing +- Drizzle ORM for database queries +- Zod for validation +- Better Auth for authentication +- React Server Components (default) +- `nuqs` for URL state +- shadcn/ui for UI components + +**Forbidden patterns** (do not introduce): +- ❌ New state management libraries (Redux, Zustand, MobX) +- ❌ Database access outside `@turbostarter/db/server` +- ❌ Bypassing API layer from apps (direct DB access) +- ❌ Business logic in React components +- ❌ Client-side auth checks as security boundaries +- ❌ Ad-hoc environment variable loading (use existing patterns) +- ❌ New packages without justification (see "Adding a new package") +- ❌ Runtime schema mutations or direct SQL queries + +**When in doubt**: Ask the user before introducing new dependencies, patterns, or architectural changes. + +## Reuse-First Principle + +**Critical rule**: Always search for and reuse existing implementations before creating new ones. + +### Before implementing ANY feature, check: + +1. **Existing UI components** (`packages/ui/web/src/`, `packages/ui/mobile/src/`): + - Check for similar components (forms, buttons, modals, tables) + - Use shadcn/ui components via `pnpm --filter @turbostarter/ui-web ui:add` + - Don't recreate what already exists in the UI packages + +2. **Existing utilities** (`packages/shared/src/`): + - Common functions, hooks, constants + - Check `packages/shared/src/utils/` before writing helpers + - Check `packages/shared/src/hooks/` before creating custom hooks + +3. **Existing API patterns** (`packages/api/src/modules/`): + - Look at similar endpoints (users, organizations, admin) + - Reuse query/mutation patterns from existing modules + - Use established error handling and validation patterns + +4. **Existing database queries** (`packages/db/src/`): + - Check for similar queries in other modules + - Reuse Drizzle query patterns + - Don't duplicate relationship definitions + +5. **Existing patterns in similar pages**: + - Dashboard pages: check `apps/web/src/app/[locale]/dashboard/` + - Admin pages: check `apps/web/src/app/[locale]/admin/` + - Auth pages: check `apps/web/src/app/[locale]/auth/` + - Settings pages: check sub-navigation patterns + +### Search workflow before implementing: + +```bash +# Search for similar functionality +grep -r "keyword" packages/ +grep -r "ComponentName" apps/web/src/ + +# Check UI components +ls packages/ui/web/src/ +ls packages/ui/mobile/src/ + +# Check utilities +ls packages/shared/src/utils/ +ls packages/shared/src/hooks/ +``` + +### Examples of reuse over reimplementation: + +❌ **Bad - Reimplementing**: +```tsx +// Creating a new button variant when one exists +export function MyCustomButton() { + return ; +} +``` + +✅ **Good - Reusing**: +```tsx +// Using existing button with variant +import { Button } from "@turbostarter/ui-web/button"; +export function MyFeature() { + return ; +} +``` + +❌ **Bad - Duplicating logic**: +```tsx +// Writing custom date formatter +function formatDate(date: Date) { + return date.toLocaleDateString(); +} +``` + +✅ **Good - Using existing utility**: +```tsx +// Check if packages/shared has formatDate first +import { formatDate } from "@turbostarter/shared/utils"; +``` + +❌ **Bad - Creating new API pattern**: +```tsx +// Inventing new error handling +if (!user) throw new Error("Not found"); +``` + +✅ **Good - Following existing patterns**: +```tsx +// Copy pattern from packages/api/src/modules/admin/users/ +if (!user) { + return c.json({ error: "User not found" }, 404); +} +``` + +### Decision tree for new implementations: + +``` +Does similar functionality exist? +├─ YES → Reuse or extend it +│ └─ Can you extend the existing component/utility? +│ ├─ YES → Add props/options to existing code +│ └─ NO → Compose with existing primitives +└─ NO → Implement new, but: + ├─ Follow established patterns from similar code + ├─ Use existing primitives (UI components, utilities) + └─ Make it reusable for future needs +``` + +### What this prevents: + +- ❌ Duplicate button/input/modal components +- ❌ Multiple implementations of the same utility function +- ❌ Inconsistent API response formats +- ❌ Different auth/validation patterns across features +- ❌ Reimplementing data table patterns +- ❌ Creating custom hooks that already exist + +### Required agent behavior: + +Before implementing ANY feature: +1. **Search** the codebase for similar implementations +2. **Read** existing code in the same domain (admin, dashboard, auth) +3. **Ask** the user if you're unsure whether something exists +4. **Reuse** existing components, utilities, and patterns +5. **Only create new** when genuinely needed and nothing similar exists + +**Optimization target**: Minimize code duplication and maximize consistency through reuse. + +## Code Conventions + +### TypeScript +- Write concise, technical TypeScript code +- Prefer functional and declarative patterns over classes +- Use interfaces over type aliases +- Avoid enums; use const objects with `as const` instead +- Descriptive variable names with auxiliary verbs (e.g., `isLoading`, `hasError`) +- Prefer iteration and modularization over code duplication + +### React (Web) +- **Favor React Server Components** (default in Next.js App Router) +- Minimize `"use client"` directive - only when necessary for: + - Browser APIs (localStorage, window) + - Event handlers and interactivity + - React hooks (useState, useEffect) +- Keep `use client` scoped to specific components; avoid at layout level +- Minimize client-side state (`useState`, `useEffect`) +- Wrap client components in `Suspense` with fallbacks +- Use dynamic imports for non-critical client components +- Use Tailwind CSS for styling; mobile-first responsive design +- Use Shadcn/Radix UI components from `@turbostarter/ui-web` +- Image optimization: WebP format, responsive sizes, lazy loading +- Focus on Web Vitals: LCP, CLS, FID +- Use `nuqs` for URL search param state management + +### React Native (Mobile) +- Use safe area primitives: `SafeAreaProvider`, `SafeAreaView`, scroll variants +- Minimize `useState` and `useEffect`; prefer memoization +- Use `React.memo`, `useMemo`, `useCallback` for performance +- Expo Router for file-based navigation +- Use Expo SplashScreen for loading states +- Optimize images: `expo-image` package, WebP format +- Shared UI components from `@turbostarter/ui-mobile` + +### File Organization +1. Exported component/function first +2. Sub-components below +3. Helper functions +4. Static content +5. Types/interfaces at bottom + +### Error Handling +- Use guard clauses and early returns +- Expected errors: model as return values in Server Actions +- Unexpected errors: let error boundaries catch +- API: Use Zod for input validation +- Handle edge cases early in function logic + +### Imports +- Use path aliases: + - Apps: `~/` maps to `src/` + - Packages: `@turbostarter/` +- Group imports: external → internal → types +- Add imports and types explicitly; avoid `any` and unsafe casts + +### Code Quality +- Adhere to existing formatting; do not reformat unrelated code +- Match existing code style and patterns +- Keep components small and modular +- When modifying multiple areas, prefer creating shared helpers in `packages/` to avoid duplication + +### AI Agent Guidelines + +When working autonomously, prioritize: + +**What to do**: +- ✅ Prefer modifying existing patterns over creating new ones +- ✅ Keep changes minimal and scoped to the specific task +- ✅ Preserve existing functionality unless explicitly asked to change it +- ✅ Follow the established patterns in similar files +- ✅ Ask for clarification when requirements conflict with this document +- ✅ Use TypeScript's type system to catch errors early + +**What to avoid**: +- ❌ Large refactors without explicit instruction +- ❌ Introducing breaking changes to APIs or schemas silently +- ❌ "Improving" code that isn't part of the task +- ❌ Making assumptions when requirements are ambiguous +- ❌ Adding dependencies without justification + +**Optimize for**: Maintainability, type safety, and consistency with existing architecture over "clever" solutions. + +## Key Workflows + +### Adding a new API endpoint +1. Create module in `packages/api/src/modules//` +2. Define router with Hono, add queries/mutations +3. Export router from module +4. Mount in `packages/api/src/index.ts` +5. Client auto-gets types via Hono RPC + +### Database schema changes +1. Edit schema in `packages/db/src/schema/` +2. Generate migration: `pnpm with-env -F @turbostarter/db db:generate` +3. Review generated SQL in `packages/db/migrations/` +4. Apply: `pnpm with-env -F @turbostarter/db db:migrate` +5. Verify in Studio: `pnpm with-env -F @turbostarter/db db:studio` + +### Adding a new dashboard page + +**1. Define path in `apps/web/src/config/paths.ts`**: +```ts +// For user dashboard +dashboard: { + user: { + myFeature: `${DASHBOARD_PREFIX}/my-feature`, + } +} + +// For organization dashboard +organization: (slug: string) => ({ + myFeature: `${DASHBOARD_PREFIX}/${slug}/my-feature`, +}) + +// For admin dashboard +admin: { + myResource: { + index: `${ADMIN_PREFIX}/my-resource`, + detail: (id: string) => `${ADMIN_PREFIX}/my-resource/${id}`, + } +} +``` + +**2. Add sidebar menu item in layout**: +- User dashboard: `apps/web/src/app/[locale]/dashboard/(user)/layout.tsx` +- Organization: `apps/web/src/app/[locale]/dashboard/[organization]/layout.tsx` +- Admin: `apps/web/src/app/[locale]/admin/layout.tsx` + +```ts +const menu = [ + { + label: "platform", // or "account", "organization", "admin" + items: [ + { + title: "myFeature", // i18n key from common.json + href: pathsConfig.dashboard.user.myFeature, + icon: Icons.YourIcon, // from @turbostarter/ui-web/icons + }, + ], + }, +]; +``` + +**3. Create page file** with standard structure: + +**Basic page** (`page.tsx`): +```tsx +import { getTranslation } from "@turbostarter/i18n/server"; +import { getMetadata } from "~/lib/metadata"; +import { + DashboardHeader, + DashboardHeaderTitle, + DashboardHeaderDescription, +} from "~/modules/common/layout/dashboard/header"; + +export const generateMetadata = getMetadata({ + title: "common:myFeature", + description: "dashboard:myFeature.description", +}); + +export default async function MyFeaturePage() { + const { t } = await getTranslation({ ns: "dashboard" }); + + return ( + <> + +
+ {t("myFeature.title")} + + {t("myFeature.description")} + +
+
+ + {/* Page content */} + + ); +} +``` + +**Data table page** (admin/list pages): +```tsx +import { createSearchParamsCache, parseAsInteger } from "nuqs/server"; +import { Suspense } from "react"; +import { handle } from "@turbostarter/api/utils"; +import { DataTableSkeleton } from "@turbostarter/ui-web/data-table/data-table-skeleton"; +import { api } from "~/lib/api/server"; + +const searchParamsCache = createSearchParamsCache({ + page: parseAsInteger.withDefault(1), + perPage: parseAsInteger.withDefault(10), + sort: getSortingStateParser().withDefault([{ id: "name", desc: false }]), + q: parseAsString, +}); + +export default async function MyResourcesPage(props: { + searchParams: Promise>; +}) { + const searchParams = await props.searchParams; + const { page, perPage, sort, ...filters } = searchParamsCache.parse(searchParams); + + const promise = handle(api.admin.myResources.$get)({ + query: { page: page.toString(), perPage: perPage.toString(), sort: JSON.stringify(sort) }, + }); + + return ( + <> + + My Resources + + }> + + + + ); +} +``` + +**4. Add sub-navigation** (for settings-like pages): + +Create `layout.tsx` with `SettingsNav` pattern: +```tsx +const LINKS = [ + { label: "general", href: pathsConfig.myFeature.general }, + { label: "advanced", href: pathsConfig.myFeature.advanced }, +] as const; + +export default async function MyFeatureLayout({ children }) { + const { t } = await getTranslation(); + + return ( + <> + + {t("myFeature.title")} +
+ ({ ...link, label: t(link.label) }))} /> +
+
+
+
+
+ ({ ...link, label: t(link.label) }))} /> +
+
+
{children}
+
+ + ); +} +``` + +**5. Add translations**: +- Common labels: `packages/i18n/translations/en/common.json` +- Page content: `packages/i18n/translations/en/dashboard.json` or `admin.json` + +```json +{ + "myFeature": "My Feature", + "myFeature.title": "Feature Title", + "myFeature.description": "Feature description" +} +``` + +### Adding a new package to monorepo + +**When to add a new package** (advanced): +- Only when functionality needs to be shared across multiple apps +- NOT for adding pages/components to a single app (use `apps/web/src/` instead) +- NOT for modifying existing packages + +**Steps**: + +1. **Generate package**: +```bash +turbo gen package +# Enter package name (e.g., "example" → @turbostarter/example) +``` + +2. **Enable fast refresh** in `apps/web/next.config.ts`: +```ts +const INTERNAL_PACKAGES = [ + // ...existing packages + "@turbostarter/example", +]; +``` + +3. **Define exports** in `package.json`: +```json +{ + "exports": { + ".": "./src/index.ts", // Default export + "./client": "./src/client.ts", // Client-only code + "./server": "./src/server.ts" // Server-only code + } +} +``` + +**Why separate exports** (client/server pattern): +- Better tree-shaking (avoid bundling server code in client) +- Clear separation of concerns +- Used in existing packages like `@turbostarter/db` (has `/server` export) + +**Usage**: +```tsx +// Default export +import { example } from "@turbostarter/example"; + +// Named exports (better tree-shaking) +import { clientFn } from "@turbostarter/example/client"; +import { serverFn } from "@turbostarter/example/server"; +``` + +### Adding a new app to monorepo + +**When to add a new app** (very advanced): +- Only when you need multiple web apps sharing the same infrastructure +- Want to keep pulling updates from TurboStarter for the base `apps/web` +- Alternative: Create a separate repository (often simpler) + +**Use git subtree workflow**: + +1. **Create subtree** from `apps/web` (one-time setup): +```bash +git subtree split --prefix=apps/web --branch web-branch +``` + +2. **Add new app** using web as template: +```bash +# Example: create apps/ai-chat from apps/web template +git subtree add --prefix=apps/ai-chat origin web-branch --squash +``` + +3. **Update new app** when pulling TurboStarter updates: +```bash +# Pull latest from TurboStarter +git pull upstream main + +# Update web-branch with latest apps/web +git subtree split --prefix=apps/web --branch web-branch +git push origin web-branch + +# Pull updates into your new app +git subtree pull --prefix=apps/ai-chat origin web-branch --squash +``` + +**Why this approach**: +- Keeps new apps in sync with base web app structure +- Allows selective updates (can modify ai-chat independently) +- Maintains ability to pull upstream TurboStarter updates + +### Multi-platform development +- Share logic in `packages/` to avoid duplication +- UI components: separate web (`ui-web`) and mobile (`ui-mobile`) packages +- API client works across web and mobile with same types + +## Testing + +- Test framework: Vitest +- Unit tests: co-located with source (`*.test.ts`) +- Run tests: `pnpm test` (uses Turbo caching) +- Watch mode: `pnpm test:projects:watch` + +## Troubleshooting + +### Common Issues + +**Node/pnpm version mismatch**: +- Ensure Node >= 22.17.0: `node -v` +- Ensure pnpm 10.25.0: `pnpm -v` + +**Services not available / connection refused**: +- Ensure Docker is running +- Start services: `pnpm services:start` +- Check logs: `pnpm services:logs` +- Verify status: `pnpm services:status` + +**DATABASE_URL or env not loaded**: +- Create `.env` at repo root (not `.env.local`) +- Use `pnpm with-env` prefix for all DB commands +- Check `turbo.json` for required `globalEnv` variables + +**Turbo or module resolution issues after refactors**: +- Clear caches: `pnpm clean` +- Reinstall: `pnpm install` + +**Migration drift or conflicts**: +- Check status: `pnpm with-env -F @turbostarter/db db:check` +- Re-generate migration: `db:generate` +- Apply: `db:migrate` + +## Performance Tips + +- **Prefer targeted commands**: Use `pnpm --filter ` to minimize work +- **Use `pnpm with-env`** whenever a command depends on environment variables +- **Leverage Turbo caching**: Commands like `build`, `lint`, `test` are cached +- **Web app**: Prefer React Server Components to reduce client bundle +- **Mobile app**: Memoize components and callbacks to prevent unnecessary re-renders + +## Important Notes + +- **Never commit `.env` files** - use `.env.example` as templates +- **Always use `pnpm with-env`** for database commands +- **Docker must be running** for local development (PostgreSQL) +- **Node.js >= 22.17.0** required +- **pnpm 10.25.0** is the package manager (enforced via `packageManager` field) +- Conventional Commits enforced via commitlint (husky hook) +- Workspace validation runs on `postinstall` via sherif diff --git a/.context/turbostarter-framework-context/index.md b/.context/turbostarter-framework-context/index.md new file mode 100644 index 0000000..a2e763c --- /dev/null +++ b/.context/turbostarter-framework-context/index.md @@ -0,0 +1,698 @@ +# TurboStarter Documentation Index + +**Last updated:** 2025-12-21 13:11 +**Total pages:** 222 +**Source:** https://www.turbostarter.dev/llms.txt + +--- + +## Quick Reference + +Use this index to find TurboStarter documentation. Each link includes a description. + +### Categories Overview + +| Platform | Pages | Key Topics | +|----------|-------|------------| +| **Ai** | 24 | docs | +| **Extension** | 52 | ai, analytics, api, auth, billing (+17 more) | +| **Mobile** | 55 | ai, analytics, api, auth, billing (+17 more) | +| **Web** | 91 | admin, ai, analytics, api, auth (+21 more) | + +--- + +## Ai + +### Docs + +*24 pages covering docs functionality.* + +| Topic | Description | +|-------|-------------| +| [API](sections/ai/docs/api.md) | Overview of the API service in TurboStarter AI, including its architecture, tech... | +| [Agents](sections/ai/docs/agents.md) | Build powerful, autonomous AI agents capable of performing complex tasks within ... | +| [Anthropic](sections/ai/docs/anthropic.md) | Setup Anthropic provider and learn how to use it in the starter kit. | +| [Architecture](sections/ai/docs/architecture.md) | A quick overview of the different parts of the TurboStarter AI. | +| [Authentication](sections/ai/docs/auth.md) | Learn about the authentication flow in TurboStarter AI. | +| [Billing](sections/ai/docs/billing.md) | Discover how to manage billing and payment methods for AI features. | +| [Chat with PDF](sections/ai/docs/pdf.md) | Engage in conversations with your PDF documents using AI to extract insights and... | +| [Chatbot](sections/ai/docs/chat.md) | Build a powerful AI assistant with multiple LLMs, generative UI, web browsing, a... | +| [Database](sections/ai/docs/database.md) | Overview of the database service in TurboStarter AI. | +| [DeepSeek](sections/ai/docs/deepseek.md) | Integrate DeepSeek's powerful AI models into your applications with minimal setu... | +| [Eleven Labs](sections/ai/docs/eleven-labs.md) | Setup ElevenLabs and learn how to integrate its AI audio capabilities into the s... | +| [Get started](sections/ai/docs.md) | An overview of the TurboStarter AI starter kit. | +| [Google AI](sections/ai/docs/google.md) | Setup Google Generative AI provider and learn how to use its models like Gemini ... | +| [Image Generation](sections/ai/docs/image.md) | Learn how to generate images using AI models within the TurboStarter AI demo app... | +| [Internationalization](sections/ai/docs/internationalization.md) | Learn how we manage internationalization in TurboStarter AI. | +| [Meta](sections/ai/docs/meta.md) | Setup Meta's Llama models and learn how to use them in the starter kit via vario... | +| [OpenAI](sections/ai/docs/openai.md) | Setup OpenAI provider and learn how to use it in the starter kit. | +| [Replicate](sections/ai/docs/replicate.md) | Setup Replicate provider and learn how to use it in the starter kit. | +| [Security](sections/ai/docs/security.md) | Learn about the security measures implemented in TurboStarter AI. | +| [Storage](sections/ai/docs/storage.md) | Explore cloud storage services for AI applications. | +| [Tech stack](sections/ai/docs/stack.md) | Learn which tools and libraries power TurboStarter AI. | +| [Text to Speech](sections/ai/docs/tts.md) | Convert text into natural-sounding speech using advanced AI voice synthesis mode... | +| [UI](sections/ai/docs/ui.md) | Learn more about UI components and design system in AI starter kit. | +| [xAI Grok](sections/ai/docs/xai.md) | Setup xAI provider and learn how to use it in the starter kit. | + +## Extension + +### Ai + +| Topic | Description | +|-------|-------------| +| [AI](sections/extension/ai.md) | Leverage AI in your TurboStarter extension. | + +### Analytics + +| Topic | Description | +|-------|-------------| +| [Configuration](sections/extension/analytics/configuration.md) | Learn how to configure extension analytics in TurboStarter. | +| [Overview](sections/extension/analytics/overview.md) | Get started with extension analytics in TurboStarter. | +| [Tracking events](sections/extension/analytics/tracking.md) | Learn how to track events in your TurboStarter extension. | + +### Api + +| Topic | Description | +|-------|-------------| +| [Overview](sections/extension/api/overview.md) | Get started with the API. | +| [Using API client](sections/extension/api/client.md) | How to use API client to interact with the API. | + +### Auth + +| Topic | Description | +|-------|-------------| +| [Overview](sections/extension/auth/overview.md) | Learn how to authenticate users in your extension. | +| [Session](sections/extension/auth/session.md) | Learn how to manage the user session in your extension. | + +### Billing + +| Topic | Description | +|-------|-------------| +| [Billing](sections/extension/billing.md) | Get started with billing in TurboStarter. | + +### Cli + +| Topic | Description | +|-------|-------------| +| [CLI](sections/extension/cli.md) | Start your new app project with a single command. | + +### Configuration + +| Topic | Description | +|-------|-------------| +| [App configuration](sections/extension/configuration/app.md) | Learn how to setup the overall settings of your extension. | +| [Environment variables](sections/extension/configuration/environment-variables.md) | Learn how to configure environment variables. | +| [Manifest](sections/extension/configuration/manifest.md) | Learn how to configure the manifest of your extension. | + +### Customization + +*4 pages covering customization functionality.* + +| Topic | Description | +|-------|-------------| +| [Adding apps](sections/extension/customization/add-app.md) | Learn how to add apps to your Turborepo workspace. | +| [Adding packages](sections/extension/customization/add-package.md) | Learn how to add packages to your Turborepo workspace. | +| [Components](sections/extension/customization/components.md) | Manage and customize your extension components. | +| [Styling](sections/extension/customization/styling.md) | Get started with styling your extension. | + +### Database + +| Topic | Description | +|-------|-------------| +| [Database](sections/extension/database.md) | Get started with the database. | + +### Extras + +| Topic | Description | +|-------|-------------| +| [Extras](sections/extension/extras.md) | See what you get together with the code. | + +### Faq + +| Topic | Description | +|-------|-------------| +| [FAQ](sections/extension/faq.md) | Find answers to common technical questions. | + +### Installation + +*8 pages covering installation functionality.* + +| Topic | Description | +|-------|-------------| +| [Cloning repository](sections/extension/installation/clone.md) | Get the code to your local machine and start developing your extension. | +| [Common commands](sections/extension/installation/commands.md) | Learn about common commands you need to know to work with the extension project. | +| [Conventions](sections/extension/installation/conventions.md) | Some standard conventions used across TurboStarter codebase. | +| [Development](sections/extension/installation/development.md) | Get started with the code and develop your browser extension. | +| [Editor setup](sections/extension/installation/editor-setup.md) | Learn how to set up your editor for the fastest development experience. | +| [Managing dependencies](sections/extension/installation/dependencies.md) | Learn how to manage dependencies in your project. | +| [Project structure](sections/extension/installation/structure.md) | Learn about the project structure and how to navigate it. | +| [Updating codebase](sections/extension/installation/update.md) | Learn how to update your codebase to the latest version. | + +### Internationalization + +| Topic | Description | +|-------|-------------| +| [Internationalization](sections/extension/internationalization.md) | Learn how to internationalize your extension. | + +### Marketing + +| Topic | Description | +|-------|-------------| +| [Marketing](sections/extension/marketing.md) | Learn how to market your mobile application. | + +### Monitoring + +| Topic | Description | +|-------|-------------| +| [Overview](sections/extension/monitoring/overview.md) | Get started with browser extension monitoring in TurboStarter. | +| [PostHog](sections/extension/monitoring/posthog.md) | Learn how to setup PostHog as your browser extension monitoring provider. | +| [Sentry](sections/extension/monitoring/sentry.md) | Learn how to setup Sentry as your browser extension monitoring provider. | + +### Organizations + +| Topic | Description | +|-------|-------------| +| [Organizations/teams](sections/extension/organizations.md) | Learn how to use organizations/teams/multi-tenancy in TurboStarter extension. | + +### Overview + +| Topic | Description | +|-------|-------------| +| [Introduction](sections/extension.md) | Get started with TurboStarter extension kit. | + +### Publishing + +*5 pages covering publishing functionality.* + +| Topic | Description | +|-------|-------------| +| [Checklist](sections/extension/publishing/checklist.md) | Let's publish your TurboStarter extension to stores! | +| [Chrome Web Store](sections/extension/publishing/chrome.md) | Publish your extension to Google Chrome Web Store. | +| [Edge Add-ons](sections/extension/publishing/edge.md) | Publish your extension to Microsoft Edge Add-ons. | +| [Firefox Add-ons](sections/extension/publishing/firefox.md) | Publish your extension to Mozilla Firefox Add-ons. | +| [Updates](sections/extension/publishing/updates.md) | Learn how to update your published extension. | + +### Recipes + +| Topic | Description | +|-------|-------------| +| [Supabase](sections/extension/recipes/supabase.md) | Learn how to set up Supabase as the database (and optional storage) provider for... | + +### Stack + +| Topic | Description | +|-------|-------------| +| [Tech Stack](sections/extension/stack.md) | A detailed look at the technical details. | + +### Structure + +*6 pages covering structure functionality.* + +| Topic | Description | +|-------|-------------| +| [Background service worker](sections/extension/structure/background.md) | Configure your extension's background service worker. | +| [Content scripts](sections/extension/structure/content-scripts.md) | Learn more about content scripts. | +| [Messaging](sections/extension/structure/messaging.md) | Communicate between your extension's components. | +| [Overview](sections/extension/structure/overview.md) | Learn about the structure of the extension app. | +| [Pages](sections/extension/structure/pages.md) | Get started with your extension's pages. | +| [Storage](sections/extension/structure/storage.md) | Learn how to store data in your extension. | + +### Tests + +| Topic | Description | +|-------|-------------| +| [E2E tests](sections/extension/tests/e2e.md) | Simulate real user scenarios across the entire stack with automated end-to-end t... | +| [Unit tests](sections/extension/tests/unit.md) | Write and run fast unit tests for individual functions and components with insta... | + +### Troubleshooting + +| Topic | Description | +|-------|-------------| +| [Installation](sections/extension/troubleshooting/installation.md) | Find answers to common extension installation issues. | +| [Publishing](sections/extension/troubleshooting/publishing.md) | Find answers to common publishing issues. | + +## Mobile + +### Ai + +| Topic | Description | +|-------|-------------| +| [AI](sections/mobile/ai.md) | Learn how to use AI integration in your mobile app. | + +### Analytics + +| Topic | Description | +|-------|-------------| +| [Configuration](sections/mobile/analytics/configuration.md) | Learn how to configure mobile analytics in TurboStarter. | +| [Overview](sections/mobile/analytics/overview.md) | Get started with mobile analytics in TurboStarter. | +| [Tracking events](sections/mobile/analytics/tracking.md) | Learn how to track events in your TurboStarter mobile app. | + +### Api + +| Topic | Description | +|-------|-------------| +| [Overview](sections/mobile/api/overview.md) | Get started with the API. | +| [Using API client](sections/mobile/api/client.md) | How to use API client to interact with the API. | + +### Auth + +*7 pages covering auth functionality.* + +| Topic | Description | +|-------|-------------| +| [Apple](sections/mobile/auth/oauth/apple.md) | Configure "Sign in with Apple" for your mobile application. | +| [Configuration](sections/mobile/auth/configuration.md) | Configure authentication for your application. | +| [Google](sections/mobile/auth/oauth/google.md) | Configure "Sign in with Google" for your mobile application. | +| [OAuth](sections/mobile/auth/oauth.md) | Get started with social authentication. | +| [Overview](sections/mobile/auth/overview.md) | Get started with authentication. | +| [Two-Factor Authentication (2FA)](sections/mobile/auth/2fa.md) | Add an extra layer of security with two-factor authentication in your mobile app... | +| [User flow](sections/mobile/auth/flow.md) | Discover the authentication flow in Turbostarter. | + +### Billing + +| Topic | Description | +|-------|-------------| +| [Billing](sections/mobile/billing.md) | Get started with billing in TurboStarter. | + +### Cli + +| Topic | Description | +|-------|-------------| +| [CLI](sections/mobile/cli.md) | Start your new project with a single command. | + +### Configuration + +| Topic | Description | +|-------|-------------| +| [App configuration](sections/mobile/configuration/app.md) | Learn how to setup the overall settings of your app. | +| [Environment variables](sections/mobile/configuration/environment-variables.md) | Learn how to configure environment variables. | +| [Paths configuration](sections/mobile/configuration/paths.md) | Learn how to configure the paths of your app. | + +### Customization + +*4 pages covering customization functionality.* + +| Topic | Description | +|-------|-------------| +| [Adding apps](sections/mobile/customization/add-app.md) | Learn how to add apps to your Turborepo workspace. | +| [Adding packages](sections/mobile/customization/add-package.md) | Learn how to add packages to your Turborepo workspace. | +| [Components](sections/mobile/customization/components.md) | Manage and customize your app components. | +| [Styling](sections/mobile/customization/styling.md) | Get started with styling your app. | + +### Database + +| Topic | Description | +|-------|-------------| +| [Database](sections/mobile/database.md) | Get started with the database. | + +### Extras + +| Topic | Description | +|-------|-------------| +| [Extras](sections/mobile/extras.md) | See what you get together with the code. | + +### Faq + +| Topic | Description | +|-------|-------------| +| [FAQ](sections/mobile/faq.md) | Find answers to common technical questions. | + +### Installation + +*9 pages covering installation functionality.* + +| Topic | Description | +|-------|-------------| +| [Cloning repository](sections/mobile/installation/clone.md) | Get the code to your local machine and start developing your app. | +| [Common commands](sections/mobile/installation/commands.md) | Learn about common commands you need to know to work with the mobile project. | +| [Conventions](sections/mobile/installation/conventions.md) | Some standard conventions used across TurboStarter codebase. | +| [Development](sections/mobile/installation/development.md) | Get started with the code and develop your mobile SaaS. | +| [Editor setup](sections/mobile/installation/editor-setup.md) | Learn how to set up your editor for the fastest development experience. | +| [Firebase project](sections/mobile/installation/firebase.md) | Learn how to set up a Firebase project for your TurboStarter mobile app. | +| [Managing dependencies](sections/mobile/installation/dependencies.md) | Learn how to manage dependencies in your project. | +| [Project structure](sections/mobile/installation/structure.md) | Learn about the project structure and how to navigate it. | +| [Updating codebase](sections/mobile/installation/update.md) | Learn how to update your codebase to the latest version. | + +### Internationalization + +| Topic | Description | +|-------|-------------| +| [Internationalization](sections/mobile/internationalization.md) | Learn how to internationalize your mobile app. | + +### Marketing + +| Topic | Description | +|-------|-------------| +| [Marketing](sections/mobile/marketing.md) | Learn how to market your mobile application. | + +### Monitoring + +| Topic | Description | +|-------|-------------| +| [Overview](sections/mobile/monitoring/overview.md) | Get started with mobile monitoring in TurboStarter. | +| [PostHog](sections/mobile/monitoring/posthog.md) | Learn how to setup PostHog as your mobile monitoring provider. | +| [Sentry](sections/mobile/monitoring/sentry.md) | Learn how to setup Sentry as your mobile monitoring provider. | + +### Organizations + +*4 pages covering organizations functionality.* + +| Topic | Description | +|-------|-------------| +| [Active organization](sections/mobile/organizations/active-organization.md) | Set and switch the current organization context within your application. | +| [Invitations](sections/mobile/organizations/invitations.md) | Send, track, and accept organization invites. | +| [Overview](sections/mobile/organizations/overview.md) | Learn how to use organizations/teams/multi-tenancy in TurboStarter mobile app. | +| [RBAC (Roles & Permissions)](sections/mobile/organizations/rbac.md) | Manage roles, permissions, and access scopes. | + +### Overview + +| Topic | Description | +|-------|-------------| +| [Introduction](sections/mobile.md) | Get started with TurboStarter mobile kit. | + +### Publishing + +*4 pages covering publishing functionality.* + +| Topic | Description | +|-------|-------------| +| [App Store (iOS)](sections/mobile/publishing/ios.md) | Learn how to publish your mobile app to the Apple App Store. | +| [Checklist](sections/mobile/publishing/checklist.md) | Let's publish your TurboStarter app to stores! | +| [Google Play (Android)](sections/mobile/publishing/android.md) | Learn how to publish your mobile app to the Google Play Store. | +| [Updates](sections/mobile/publishing/updates.md) | Learn how to update your published app. | + +### Push Notifications + +| Topic | Description | +|-------|-------------| +| [Push notifications](sections/mobile/push-notifications.md) | Engage your users with personalized notifications. | + +### Recipes + +| Topic | Description | +|-------|-------------| +| [Supabase](sections/mobile/recipes/supabase.md) | Learn how to set up Supabase as the database (and optional storage) provider for... | + +### Stack + +| Topic | Description | +|-------|-------------| +| [Tech Stack](sections/mobile/stack.md) | A detailed look at the technical details. | + +### Tests + +| Topic | Description | +|-------|-------------| +| [E2E tests](sections/mobile/tests/e2e.md) | Simulate real user scenarios across the entire stack with automated end-to-end t... | +| [Unit tests](sections/mobile/tests/unit.md) | Write and run fast unit tests for individual functions and components with insta... | + +### Troubleshooting + +| Topic | Description | +|-------|-------------| +| [Installation](sections/mobile/troubleshooting/installation.md) | Find answers to common mobile installation issues. | +| [Publishing](sections/mobile/troubleshooting/publishing.md) | Find answers to common mobile publishing issues. | + +## Web + +### Admin + +| Topic | Description | +|-------|-------------| +| [Overview](sections/web/admin/overview.md) | Get started with the admin dashboard in TurboStarter. | +| [Super Admin UI](sections/web/admin/ui.md) | Get familiar with the Super Admin dashboard and start managing your application. | + +### Ai + +| Topic | Description | +|-------|-------------| +| [Configuration](sections/web/ai/configuration.md) | Configure AI integration in your TurboStarter project. | +| [Overview](sections/web/ai/overview.md) | Get started with AI integration in your TurboStarter project. | + +### Analytics + +| Topic | Description | +|-------|-------------| +| [Configuration](sections/web/analytics/configuration.md) | Learn how to configure web analytics in TurboStarter. | +| [Overview](sections/web/analytics/overview.md) | Get started with web analytics in TurboStarter. | +| [Tracking events](sections/web/analytics/tracking.md) | Learn how to track events in your TurboStarter web app. | + +### Api + +*6 pages covering api functionality.* + +| Topic | Description | +|-------|-------------| +| [Adding new endpoint](sections/web/api/new-endpoint.md) | How to add new endpoint to the API. | +| [Internationalization](sections/web/api/internationalization.md) | Learn how to localize and translate your API. | +| [Mutations](sections/web/api/mutations.md) | Learn how to mutate data on the server. | +| [Overview](sections/web/api/overview.md) | Get started with the API. | +| [Protected routes](sections/web/api/protected-routes.md) | Learn how to protect your API routes. | +| [Using API client](sections/web/api/client.md) | How to use API client to interact with the API. | + +### Auth + +*5 pages covering auth functionality.* + +| Topic | Description | +|-------|-------------| +| [Configuration](sections/web/auth/configuration.md) | Configure authentication for your application. | +| [OAuth](sections/web/auth/oauth.md) | Get started with social authentication. | +| [Overview](sections/web/auth/overview.md) | Get started with authentication. | +| [Two-Factor Authentication (2FA)](sections/web/auth/2fa.md) | Add an extra layer of security with two-factor authentication. | +| [User flow](sections/web/auth/flow.md) | Discover the authentication flow in Turbostarter. | + +### Background Tasks + +| Topic | Description | +|-------|-------------| +| [Overview](sections/web/background-tasks/overview.md) | Learn about background tasks & cron jobs and how they can power your application... | +| [Upstash QStash](sections/web/background-tasks/qstash.md) | Integrate Upstash QStash with your TurboStarter application for serverless-first... | +| [trigger.dev](sections/web/background-tasks/trigger.md) | Integrate trigger.dev with your TurboStarter application for reliable background... | + +### Billing + +*7 pages covering billing functionality.* + +| Topic | Description | +|-------|-------------| +| [Configuration](sections/web/billing/configuration.md) | Configure billing for your application. | +| [Creem](sections/web/billing/creem.md) | Manage your customers data and subscriptions using Creem. | +| [Lemon Squeezy](sections/web/billing/lemon-squeezy.md) | Manage your customers data and subscriptions using Lemon Squeezy. | +| [Overview](sections/web/billing/overview.md) | Get started with billing in TurboStarter. | +| [Polar](sections/web/billing/polar.md) | Manage your customers data and subscriptions using Polar. | +| [Stripe](sections/web/billing/stripe.md) | Manage your customers data and subscriptions using Stripe. | +| [Webhooks](sections/web/billing/webhooks.md) | Handle webhooks from your billing provider. | + +### Cli + +| Topic | Description | +|-------|-------------| +| [CLI](sections/web/cli.md) | Start your new project with a single command. | + +### Cms + +| Topic | Description | +|-------|-------------| +| [Blog](sections/web/cms/blog.md) | Learn how to manage your blog content. | +| [Content Collections](sections/web/cms/content-collections.md) | Get started with Content Collections. | +| [Overview](sections/web/cms/overview.md) | Manage your content in TurboStarter. | + +### Configuration + +| Topic | Description | +|-------|-------------| +| [App configuration](sections/web/configuration/app.md) | Learn how to setup the overall settings of your app. | +| [Environment variables](sections/web/configuration/environment-variables.md) | Learn how to configure environment variables. | +| [Paths configuration](sections/web/configuration/paths.md) | Learn how to configure the paths of your app. | + +### Customization + +*4 pages covering customization functionality.* + +| Topic | Description | +|-------|-------------| +| [Adding apps](sections/web/customization/add-app.md) | Learn how to add apps to your Turborepo workspace. | +| [Adding packages](sections/web/customization/add-package.md) | Learn how to add packages to your Turborepo workspace. | +| [Components](sections/web/customization/components.md) | Manage and customize your app components. | +| [Styling](sections/web/customization/styling.md) | Get started with styling your app. | + +### Database + +*4 pages covering database functionality.* + +| Topic | Description | +|-------|-------------| +| [Database client](sections/web/database/client.md) | Use database client to interact with the database. | +| [Migrations](sections/web/database/migrations.md) | Migrate your changes to the database. | +| [Overview](sections/web/database/overview.md) | Get started with the database. | +| [Schema](sections/web/database/schema.md) | Learn about the database schema. | + +### Deployment + +*9 pages covering deployment functionality.* + +| Topic | Description | +|-------|-------------| +| [AWS Amplify](sections/web/deployment/amplify.md) | Learn how to deploy your TurboStarter app to AWS Amplify. | +| [Checklist](sections/web/deployment/checklist.md) | Let's deploy your TurboStarter app to production! | +| [Docker](sections/web/deployment/docker.md) | Learn how to containerize your TurboStarter app with Docker. | +| [Fly.io](sections/web/deployment/fly.md) | Learn how to deploy your TurboStarter app to Fly.io. | +| [Netlify](sections/web/deployment/netlify.md) | Learn how to deploy your TurboStarter app to Netlify. | +| [Railway](sections/web/deployment/railway.md) | Learn how to deploy your TurboStarter app to Railway. | +| [Render](sections/web/deployment/render.md) | Learn how to deploy your TurboStarter app to Render. | +| [Standalone API](sections/web/deployment/api.md) | Learn how to deploy your API as a dedicated service. | +| [Vercel](sections/web/deployment/vercel.md) | Learn how to deploy your TurboStarter app to Vercel. | + +### Emails + +| Topic | Description | +|-------|-------------| +| [Configuration](sections/web/emails/configuration.md) | Learn how to configure your emails in TurboStarter. | +| [Overview](sections/web/emails/overview.md) | Get started with emails in TurboStarter. | +| [Sending emails](sections/web/emails/sending.md) | Learn how to send emails in TurboStarter. | + +### Extras + +| Topic | Description | +|-------|-------------| +| [Extras](sections/web/extras.md) | See what you get together with the code. | + +### Faq + +| Topic | Description | +|-------|-------------| +| [FAQ](sections/web/faq.md) | Find answers to common technical questions. | + +### Installation + +*8 pages covering installation functionality.* + +| Topic | Description | +|-------|-------------| +| [Cloning repository](sections/web/installation/clone.md) | Get the code to your local machine and start developing. | +| [Common commands](sections/web/installation/commands.md) | Learn about common commands you need to know to work with the project. | +| [Conventions](sections/web/installation/conventions.md) | Some standard conventions used across TurboStarter codebase. | +| [Development](sections/web/installation/development.md) | Get started with the code and develop your SaaS. | +| [Editor setup](sections/web/installation/editor-setup.md) | Learn how to set up your editor for the fastest development experience. | +| [Managing dependencies](sections/web/installation/dependencies.md) | Learn how to manage dependencies in your project. | +| [Project structure](sections/web/installation/structure.md) | Learn about the project structure and how to navigate it. | +| [Updating codebase](sections/web/installation/update.md) | Learn how to update your codebase to the latest version. | + +### Internationalization + +| Topic | Description | +|-------|-------------| +| [Configuration](sections/web/internationalization/configuration.md) | Learn how to configure internationalization in TurboStarter. | +| [Overview](sections/web/internationalization/overview.md) | Get started with internationalization in TurboStarter. | +| [Translating app](sections/web/internationalization/translations.md) | Learn how to translate your application to multiple languages. | + +### Marketing + +| Topic | Description | +|-------|-------------| +| [Legal pages](sections/web/marketing/legal.md) | Learn how to create and update legal pages | +| [Marketing pages](sections/web/marketing/pages.md) | Discover which marketing pages are available out of the box and how to add a new... | +| [SEO](sections/web/marketing/seo.md) | Learn how to optimize your app for search engines. | + +### Monitoring + +| Topic | Description | +|-------|-------------| +| [Overview](sections/web/monitoring/overview.md) | Get started with web monitoring in TurboStarter. | +| [PostHog](sections/web/monitoring/posthog.md) | Learn how to setup PostHog as your web monitoring provider. | +| [Sentry](sections/web/monitoring/sentry.md) | Learn how to setup Sentry as your web monitoring provider. | + +### Organizations + +*5 pages covering organizations functionality.* + +| Topic | Description | +|-------|-------------| +| [Active organization](sections/web/organizations/active-organization.md) | Set and switch the current organization context within your application. | +| [Data model](sections/web/organizations/data-model.md) | Entities and relationships for organizations and multi-tenancy. | +| [Invitations](sections/web/organizations/invitations.md) | Send, track, and accept organization invites. | +| [Overview](sections/web/organizations/overview.md) | Learn how to use organizations/teams/multi-tenancy in TurboStarter. | +| [RBAC (Roles & Permissions)](sections/web/organizations/rbac.md) | Manage roles, permissions, and access scopes. | + +### Overview + +| Topic | Description | +|-------|-------------| +| [Introduction](sections/web.md) | Get started with TurboStarter web kit. | + +### Recipes + +| Topic | Description | +|-------|-------------| +| [Supabase](sections/web/recipes/supabase.md) | Learn how to set up Supabase for your TurboStarter project. | + +### Stack + +| Topic | Description | +|-------|-------------| +| [Tech Stack](sections/web/stack.md) | A detailed look at the technical details. | + +### Storage + +| Topic | Description | +|-------|-------------| +| [Configuration](sections/web/storage/configuration.md) | Learn how to configure storage in TurboStarter. | +| [Managing files](sections/web/storage/managing-files.md) | Learn how to manage files in TurboStarter. | +| [Overview](sections/web/storage/overview.md) | Get started with storage in TurboStarter. | + +### Tests + +| Topic | Description | +|-------|-------------| +| [E2E tests](sections/web/tests/e2e.md) | Simulate real user scenarios across the entire stack with automated end-to-end t... | +| [Unit tests](sections/web/tests/unit.md) | Write and run fast unit tests for individual functions and components with insta... | + +### Troubleshooting + +*4 pages covering troubleshooting functionality.* + +| Topic | Description | +|-------|-------------| +| [Billing](sections/web/troubleshooting/billing.md) | Find answers to common billing issues. | +| [Deployment](sections/web/troubleshooting/deployment.md) | Find answers to common web deployment issues. | +| [Emails](sections/web/troubleshooting/emails.md) | Find answers to common emails issues. | +| [Installation](sections/web/troubleshooting/installation.md) | Find answers to common web installation issues. | + +--- + +## Quick Lookup by Keyword + +Common searches and where to find them: + +| Keyword | Related Docs | +|---------|--------------| +| `admin` | [Overview](sections/web/admin/overview.md), [Super Admin UI](sections/web/admin/ui.md) | +| `ai` | [AI](sections/extension/ai.md), [Tech Stack](sections/extension/stack.md), [AI](sections/mobile/ai.md) (+32 more) | +| `anthropic` | [Anthropic](sections/ai/docs/anthropic.md) | +| `api` | [Using API client](sections/extension/api/client.md), [Overview](sections/extension/api/overview.md), [Using API client](sections/mobile/api/client.md) (+8 more) | +| `auth` | [Overview](sections/extension/auth/overview.md), [Two-Factor Authentication (2FA)](sections/mobile/auth/2fa.md), [Configuration](sections/mobile/auth/configuration.md) (+9 more) | +| `billing` | [Billing](sections/extension/billing.md), [Billing](sections/mobile/billing.md), [Configuration](sections/web/billing/configuration.md) (+4 more) | +| `chat` | [Chatbot](sections/ai/docs/chat.md), [Chat with PDF](sections/ai/docs/pdf.md) | +| `database` | [Database](sections/extension/database.md), [Supabase](sections/extension/recipes/supabase.md), [Database](sections/mobile/database.md) (+6 more) | +| `deploy` | [AWS Amplify](sections/web/deployment/amplify.md), [Standalone API](sections/web/deployment/api.md), [Checklist](sections/web/deployment/checklist.md) (+6 more) | +| `docker` | [Docker](sections/web/deployment/docker.md) | +| `email` | [Configuration](sections/web/emails/configuration.md), [Overview](sections/web/emails/overview.md), [Sending emails](sections/web/emails/sending.md) (+1 more) | +| `endpoint` | [Adding new endpoint](sections/web/api/new-endpoint.md) | +| `file` | [Managing files](sections/web/storage/managing-files.md) | +| `migration` | [Migrations](sections/web/database/migrations.md) | +| `oauth` | [OAuth](sections/mobile/auth/oauth.md), [OAuth](sections/web/auth/oauth.md) | +| `openai` | [OpenAI](sections/ai/docs/openai.md) | +| `organization` | [Organizations/teams](sections/extension/organizations.md), [Active organization](sections/mobile/organizations/active-organization.md), [Invitations](sections/mobile/organizations/invitations.md) (+5 more) | +| `payment` | [Billing](sections/ai/docs/billing.md) | +| `permission` | [RBAC (Roles & Permissions)](sections/mobile/organizations/rbac.md), [RBAC (Roles & Permissions)](sections/web/organizations/rbac.md) | +| `role` | [RBAC (Roles & Permissions)](sections/mobile/organizations/rbac.md), [RBAC (Roles & Permissions)](sections/web/organizations/rbac.md) | +| `route` | [Protected routes](sections/web/api/protected-routes.md) | +| `session` | [Session](sections/extension/auth/session.md) | +| `storage` | [Supabase](sections/extension/recipes/supabase.md), [Storage](sections/extension/structure/storage.md), [Supabase](sections/mobile/recipes/supabase.md) (+3 more) | +| `stripe` | [Stripe](sections/web/billing/stripe.md) | +| `subscription` | [Creem](sections/web/billing/creem.md), [Lemon Squeezy](sections/web/billing/lemon-squeezy.md), [Polar](sections/web/billing/polar.md) (+1 more) | +| `team` | [Organizations/teams](sections/extension/organizations.md), [Overview](sections/mobile/organizations/overview.md), [Overview](sections/web/organizations/overview.md) | +| `test` | [Editor setup](sections/extension/installation/editor-setup.md), [Updating codebase](sections/extension/installation/update.md), [E2E tests](sections/extension/tests/e2e.md) (+9 more) | +| `user` | [Overview](sections/extension/auth/overview.md), [Session](sections/extension/auth/session.md), [E2E tests](sections/extension/tests/e2e.md) (+5 more) | +| `vercel` | [Vercel](sections/web/deployment/vercel.md) | diff --git a/.context/turbostarter-framework-context/llms-full.txt b/.context/turbostarter-framework-context/llms-full.txt new file mode 100644 index 0000000..2fcf2a2 --- /dev/null +++ b/.context/turbostarter-framework-context/llms-full.txt @@ -0,0 +1,25088 @@ +--- +url: /docs/extension/ai +title: AI +description: Leverage AI in your TurboStarter extension. +--- + +When it comes to AI within the browser extension, we can differentiate two approaches: + +* **Server + client**: Traditional implementation, same as for [web](/docs/web/ai/overview) and [mobile](/docs/mobile/ai), used to stream responses generated on the server to the client. +* **Chrome built-in AI**: An [experimental implementation](https://developer.chrome.com/docs/ai/built-in) of [Gemini Nano](https://blog.google/technology/ai/google-gemini-ai/#performance) that's built into new versions of the Google Chrome browser. + +We recommend relying more on the traditional server + client approach, as it's more versatile and easier to implement. Chrome's built-in AI is a nice feature, but it's still experimental and has some limitations. + +Of course, you can always implement a *hybrid* approach which combines both solutions to achieve the best results. + +## Server + client + +The traditional usage of AI integration in the browser extension is the same as for [web app](/docs/web/ai/configuration#client-side) and [mobile app](/docs/mobile/ai). We use the exact same [API endpoint](/docs/web/ai/configuration#api-endpoint), and we leverage streaming to display answers incrementally to the user as they're generated. + +```tsx title="main.tsx" +import { useChat } from "@ai-sdk/react"; +import { DefaultChatTransport } from "ai"; + +const Popup = () => { + const { messages } = useChat({ + transport: new DefaultChatTransport({ + api: "/api/ai/chat", + }), + }); + + return ( +
+ {messages.map((message) => ( +
+ {message.parts.map((part, i) => { + switch (part.type) { + case "text": + return
{part.text}
; + } + })} +
+ ))} +
+ ); +}; + +export default Popup; +``` + +It's the most reliable and recommended way to use AI in the browser extension. Feel free to reuse or modify it to suit your specific needs. + +## Chrome built-in AI + + + Chrome's implementation of [built-in AI with Gemini Nano](https://developer.chrome.com/docs/ai/built-in) is experimental and will change as they test and address feedback. + + +Chrome's built-in AI is a preview feature. To use it, you need Chrome version 127 or greater and you must enable these flags: + +* [chrome://flags/#prompt-api-for-gemini-nano](chrome://flags/#prompt-api-for-gemini-nano): `Enabled` +* [chrome://flags/#optimization-guide-on-device-model](chrome://flags/#optimization-guide-on-device-model): `Enabled BypassPrefRequirement` +* [chrome://components/](chrome://components/): Click `Optimization Guide On Device Model` to download the model. + +Once enabled, you'll be able to use `window.ai` to access the built-in AI and do things like this: + +![Chrome built-in AI](/images/docs/extension/ai.gif) + +You can even use a [dedicated provider](https://sdk.vercel.ai/providers/community-providers/chrome-ai) from the Vercel AI SDK ecosystem to simplify its usage. Please remember that this API is still in its early stages and might change in the future. + + + The best thing is that you can use this API in every part of your extension, e.g., popup, background service worker, etc. + + It's completely safe to use on the client-side, as we're not exposing any sensitive data to the user (such as the API key in the traditional server + client approach). + + +To learn more, please check out the official [Chrome documentation](https://developer.chrome.com/docs/ai/built-in) and the articles listed below. + + + + + + + + +--- +url: /docs/extension/analytics/configuration +title: Configuration +description: Learn how to configure extension analytics in TurboStarter. +--- + +The `@turbostarter/analytics-extension` package offers a streamlined and flexible approach to tracking events in your TurboStarter extension using various analytics providers. It abstracts the complexities of different analytics services and provides a consistent interface for event tracking. + +In this section, we'll guide you through the configuration process for each supported provider. + +Note that the configuration is validated against a schema, so you'll see error messages in the console if anything is misconfigured. + +## Providers + +Below, you'll find detailed information on how to set up and use each supported provider. Choose the one that best suits your needs and follow the instructions in the respective accordion section. + + + + To use Google Analytics as your analytics provider, you need to [create a Google Analytics account](https://analytics.google.com/) and [set up a property](https://support.google.com/analytics/answer/9304153). + + Next, add a data stream in your Google Analytics account settings: + + 1. Navigate to [Google Analytics](https://analytics.google.com/). + 2. In the *Admin* section, under *Data collection and modification*, click on *Data Streams*. + 3. Click *Add stream*. + 4. Select *Web* as the platform. + 5. Enter the required details for the stream (at minimum, provide a name and website URL). + 6. Click *Create stream*. + + After creating the stream, you'll need two pieces of information: + + 1. Your [Measurement ID](https://support.google.com/analytics/answer/12270356) (it should look like `G-XXXXXXXXXX`): + + ![Google Analytics Measurement ID](/images/docs/web/analytics/google/id.png) + + 2. Your [Measurement Protocol API secret](https://support.google.com/analytics/answer/9814495): + + ![Google Analytics Measurement Protocol API secret](/images/docs/web/analytics/google/api-secret.png) + + Set these values in your `.env.local` file in the `apps/extension` directory and in your CI/CD provider secrets: + + ```dotenv + VITE_GOOGLE_ANALYTICS_MEASUREMENT_ID="your-measurement-id" + VITE_GOOGLE_ANALYTICS_SECRET="your-measurement-protocol-api-secret" + ``` + + Also, make sure to activate the Google Analytics provider as your analytics provider by updating the exports in: + + ```ts title="index.ts" + // [!code word:google-analytics] + export * from "./google-analytics"; + export * from "./google-analytics/env"; + ``` + + To customize the provider, you can find its definition in `packages/analytics/extension/src/providers/google-analytics` directory. + + For more information, please refer to the [Google Analytics documentation](https://developers.google.com/analytics). + + ![Google Analytics dashboard](/images/docs/web/analytics/google/dashboard.jpg) + + + + + PostHog is also one of pre-configured providers for [monitoring](/docs/extension/monitoring/overview) in TurboStarter. You can learn more about it [here](/docs/extension/monitoring/posthog). + + + To use PostHog as your analytics provider, you need to configure a PostHog instance. You can obtain the [Cloud](https://app.posthog.com/signup) instance by [creating an account](https://app.posthog.com/signup) or [self-host](https://posthog.com/docs/self-host) it. + + Then, create a project and, based on your [project settings](https://app.posthog.com/project/settings), fill the following environment variables in your `.env.local` file in `apps/extension` directory and your CI/CD provider secrets: + + ```dotenv + VITE_POSTHOG_KEY="your-posthog-api-key" + VITE_POSTHOG_HOST="your-posthog-instance-host" + ``` + + Also, make sure to activate the PostHog provider as your analytics provider by updating the exports in: + + ```ts title="index.ts" + // [!code word:posthog] + export * from "./posthog"; + export * from "./posthog/env"; + ``` + + To customize the provider, you can find its definition in `packages/analytics/extension/src/providers/posthog` directory. + + For more information, please refer to the [PostHog documentation](https://posthog.com/docs/advanced/browser-extension). + + ![PostHog dashboard](/images/docs/web/analytics/posthog.png) + + + + +--- +url: /docs/extension/analytics/overview +title: Overview +description: Get started with extension analytics in TurboStarter. +--- + +When it comes to extension analytics, we can distinguish between two types: + +* **Store listing analytics**: Used to track the performance of your extension's store listing (e.g., how many people have viewed your extension in the store or how many have installed it). +* **In-extension analytics**: Tracks user actions within your extension (e.g., how many users triggered your popup, how many users modified extension settings, etc.). + +The `@turbostarter/analytics-extension` package provides a set of tools to easily implement both types of analytics in your extension. + +## Store listing analytics + +Interpreting your extension's store listing metrics can help you evaluate how changes to your extension and store listing affect conversion rates. For example, you can identify countries with a high number of visitors to prioritize supporting languages for those regions. + +While each store implements a different set of metrics, there are some common ones you should be aware of: + +* **Active installs**: The number of users who have installed your extension. +* **Active users**: The number of users who have used your extension. +* **Page views**: The number of times users have viewed your extension's detail page on the respective store. + +To track more detailed metrics, you can opt in to Google Analytics in the Chrome Web Store's developer dashboard. + +You can find this option under *Additional metrics* on the *Store listing* tab of your extension's control panel: + +![Chrome Web Store - Store listing - Additional metrics](/images/docs/extension/analytics/opt-in-analytics.png) + + + The Chrome Web Store manages the account for you and makes the data available + in the Google Analytics dashboard. + + +By enabling this feature, you can optimize your extension's store listing based on metrics such as bounce rate, time on page, and more. This can lead to more installs and ultimately more users for your extension. + +To learn more about the limitations of this type of analytics and how to adjust event details, please refer to the following sections in the official documentation: + + + + + + + +## In-extension analytics + +TurboStarter comes with built-in support for tracking in-extension analytics. To learn more about each supported provider and how to configure them, see their respective sections: + + + + + + + +All configuration and setup is built-in with a unified API, allowing you to switch between providers by simply changing the exports. You can even introduce your own provider without breaking any tracking-related logic. + +In the following sections, we'll cover how to set up each provider and how to track events in your extension. + + +--- +url: /docs/extension/analytics/tracking +title: Tracking events +description: Learn how to track events in your TurboStarter extension. +--- + +The strategy for tracking events that every provider has to implement is extremely simple: + +```ts +export type AllowedPropertyValues = string | number | boolean; + +type TrackFunction = ( + event: string, + data?: Record, +) => void; + +export interface AnalyticsProviderStrategy { + track: TrackFunction; +} +``` + + + You don't need to worry much about this implementation, as all the providers are already configured for you. However, it's useful to be aware of this structure if you plan to add your own custom provider. + + +As shown above, each provider must supply the `track` function. This function is responsible for sending event data to the provider. + +To track an event in any part of your extension, simply call the `track` method, passing the event name and an optional data object: + +```tsx title="main.tsx" +import { track } from "@turbostarter/analytics-extension"; + +const Popup = () => { + return ( + + ); +}; + +export default Popup; +``` + +## Identifying users + +Linking events to specific users enables you to build a full picture of how they're using your product across different sessions, devices, and platforms. + +For identification purposes, we're extending the strategy with the `identify` and `reset` methods. They are optional and only needed if you want to identify users in your app and associate their actions with a specific user ID. + +```ts +type IdentifyFunction = ( + userId: string, + traits?: Record, +) => void; + +export interface AnalyticsProviderClientStrategy { + identify: IdentifyFunction; + reset: () => void; +} +``` + +To identify users, call the `identify` method, passing the user's ID and an optional traits object: + +```tsx +import { identify } from "@turbostarter/analytics-extension"; + +identify("user-123", { name: "John Doe" }); +``` + +This will associate all future events with the user's ID, allowing you to track user behavior and gain valuable insights into your application's usage patterns. + + + The `identify` method is configured out-of-the-box to react on changes to the user's authentication state. + + When the user is authenticated, the `identify` method will be called with the user's ID and the user's traits. When the user is logged out, the `reset` method will be called to clear the existing user identification. + + +Congratulations! You've now mastered event tracking in your TurboStarter extension. With this knowledge, you're well-equipped to analyze user behaviors and gain valuable insights into your extension's usage patterns. Happy analyzing! 📊 + + +--- +url: /docs/extension/api/client +title: Using API client +description: How to use API client to interact with the API. +--- + +In browser extension code, you can only access the API client from the **client-side.** + +When you create a new component or piece of your extension and want to fetch some data, you can use the API client to do so. + +## Creating a client + +We're creating a client-side API client in `apps/extension/src/lib/api/index.tsx` file. It's a simple wrapper around the [@tanstack/react-query](https://tanstack.com/query/latest/docs/framework/react/overview) that fetches or mutates data from the API. + +It also requires wrapping your views in a `QueryClientProvider` component to provide the API client to the rest of the components. + +We recommend to create a separate layout file, which will be used to wrap your pages. TurboStarter comes with a `layout.tsx` file in the `modules/common/layout` folder, which you can use as a template: + +```tsx title="layout.tsx" +export const Layout = ({ + children, + loadingFallback, + errorFallback, +}: LayoutProps) => { + return ( + + + {children} + + + ); +}; +``` + +Remember that every part of your extension will be mounted as a **separate** React component, so you need to wrap each of them in the `QueryClientProvider` component if you want to use the API client inside: + +```tsx title="app/popup/main.tsx" +import { Layout } from "~/modules/common/layout/layout"; + +export default function Popup() { + return {/* your popup code here */}; +} +``` + + + Inside the `apps/extension/src/lib/api/index.tsx` we're calling a function to get base url of your api, so make sure it's set correctly (especially on production) and your web api endpoint is corresponding with the name there. + + ```tsx title="index.tsx" + const getBaseUrl = () => { + return env.VITE_SITE_URL; + }; + ``` + + As you can see we're mostly relying on the [environment variables](/docs/extension/configuration/environment-variables) to get it, so there shouldn't be any issues with it, but in case, please be aware where to find it 😉 + + +## Queries + +Of course, everything comes already configured for you, so you just need to start using `api` in your components/screens. + +For example, to fetch the list of posts you can use the `useQuery` hook: + +```tsx title="posts.tsx" +import { api } from "~/lib/api"; + +export const Posts = () => { + const { data: posts, isLoading } = useQuery({ + queryKey: ["posts"], + queryFn: async () => { + const response = await api.posts.$get(); + + if (!response.ok) { + throw new Error("Failed to fetch posts!"); + } + + return response.json(); + }, + }); + + if (isLoading) { + return

Loading...

; + } + + /* do something with the data... */ + return ( +
+

{JSON.stringify(posts)}

+
+ ); +}; +``` + +It's using the `@tanstack/react-query` [useQuery API](https://tanstack.com/query/latest/docs/framework/react/reference/useQuery), so you shouldn't have any troubles with it. + + + + + + + +## Mutations + +If you want to perform a mutation in your extension code, you can use the `useMutation` hook that comes straight from the integration with [Tanstack Query](https://tanstack.com/query): + +```tsx title="modules/popup/form.tsx" +import { api } from "~/lib/api"; + +export const CreatePost = () => { + const queryClient = useQueryClient(); + const { mutate } = useMutation({ + mutationFn: async (post: PostInput) => { + const response = await api.posts.$post(post); + + if (!response.ok) { + throw new Error("Failed to create post!"); + }, + }, + onSuccess: () => { + toast.success("Post created successfully!"); + queryClient.invalidateQueries({ queryKey: ["posts"] }); + }, + }); + + return
; +}; +``` + +Here, we're also invalidating the query after the mutation is successful. This is a very important step to make sure that the data is updated in the UI. + + + + + + + +## Handling responses + +As you can see in the examples above, the [Hono RPC](https://hono.dev/docs/guides/rpc) client returns a plain `Response` object, which you can use to get the data or handle errors. However, implementing this handling in every query or mutation can be tedious and will introduce unnecessary boilerplate in your codebase. + +That's why we've developed the `handle` function that unwraps the response for you, handles errors, and returns the data in a consistent format. You can safely use it with any procedure from the API client: + + + + ```tsx + // [!code word:handle] + import { handle } from "@turbostarter/api/utils"; + + import { api } from "~/lib/api"; + + export const Posts = () => { + const { data: posts, isLoading } = useQuery({ + queryKey: ["posts"], + queryFn: handle(api.posts.$get), + }); + + if (isLoading) { + return

Loading...

; + } + + /* do something with the data... */ + return ( +
+

{JSON.stringify(posts)}

+
+ ); + }; + ``` +
+ + + ```tsx + // [!code word:handle] + import { handle } from "@turbostarter/api/utils"; + + import { api } from "~/lib/api/client"; + + export const CreatePost = () => { + const queryClient = useQueryClient(); + const { mutate } = useMutation({ + mutationFn: handle(api.posts.$post), + onSuccess: () => { + toast.success("Post created successfully!"); + queryClient.invalidateQueries({ queryKey: ["posts"] }); + }, + }); + + return ; + }; + ``` + +
+ +With this approach, you can focus on the business logic instead of repeatedly writing code to handle API responses in your browser extension components, making your extension's codebase more readable and maintainable. + +The same error handling and response unwrapping benefits apply whether you're building web, mobile, or extension interfaces - allowing you to keep your data fetching logic consistent across all platforms. + + +--- +url: /docs/extension/api/overview +title: Overview +description: Get started with the API. +--- + + + To enable communication between your WXT extension and the server in a production environment, the web application with Hono API must be deployed first. + + + + + + + + +TurboStarter is designed to be a scalable and production-ready full-stack starter kit. One of its core features is a dedicated and extensible API layer. To enable this in a type-safe manner, we chose [Hono](https://hono.dev) as the API server and client library. + + + Hono is a small, simple, and ultrafast web framework that gives you a way to + define your API endpoints with full type safety. It provides built-in + middleware for common needs like validation, caching, and CORS. It also + includes an [RPC client](https://hono.dev/docs/guides/rpc) for making + type-safe function calls from the frontend. Being edge-first, it's optimized + for serverless environments and offers excellent performance. + + +All API endpoints and their resolvers are defined in the `packages/api/` package. Here you will find a `modules` folder that contains the different feature modules of the API. Each module has its own folder and exports all its resolvers. + +For each module, we create a separate Hono route in the `packages/api/index.ts` file and aggregate all sub-routers into one main router. + +The API is then exposed as a route handler that will be provided as a Next.js API route: + +```ts title="apps/web/src/app/api/[...route]/route.ts" +import { handle } from "hono/vercel"; + +import { appRouter } from "@turbostarter/api"; + +const handler = handle(appRouter); +export { handler as GET, handler as POST }; +``` + +Learn more about how to use the API in your browser extension code in the following sections: + + +--- +url: /docs/extension/auth/overview +title: Overview +description: Learn how to authenticate users in your extension. +--- + +TurboStarter uses [Better Auth](https://better-auth.com) to handle authentication. It's a secure, production-ready authentication solution that integrates seamlessly with many frameworks and provides enterprise-grade security out of the box. + + + One of the core principles of TurboStarter is to do things **as simple as possible**, and to make everything **as performant as possible**. + + Better Auth provides an excellent developer experience with minimal configuration required, while maintaining enterprise-grade security standards. Its framework-agnostic approach and focus on performance makes it the perfect choice for TurboStarter. + + Recently, Better Auth [announced](https://www.better-auth.com/blog/authjs-joins-better-auth) an incorporation of [Auth.js (27k+ stars on Github)](https://authjs.dev/), making it even more powerful and flexible. + + +![Better Auth](/images/docs/better-auth.png) + +You can read more about Better Auth in the [official documentation](https://better-auth.com/docs). + + + To keep things simple and secure, **the extension shares the same authentication session with your web app.** + + This is a common approach used by popular services like [Notion](https://www.notion.so) and [Google Workspace](https://workspace.google.com/). The benefits include: + + * Users only need to sign in once through the web app + * The extension automatically inherits the authenticated session + * Sign out actions are synchronized across platforms + * Reduced security surface area and complexity + + +Before setting up extension authentication, make sure to first [configure authentication for your web app](/docs/web/auth/overview) and then head back to the extension code. + +The following sections cover everything you need to know about authentication in your extension: + + + + + + + + + + + + +--- +url: /docs/extension/auth/session +title: Session +description: Learn how to manage the user session in your extension. +--- + +We're not implementing fully-featured auth flow in the extension. Instead, **we're sharing the same auth session with the web app.** + +It's a common practice in the industry used e.g. by [Notion](https://www.notion.so) and [Google Workspace](https://workspace.google.com/). + +That way, when the user is signed in to the web app, the extension can use the same session to authenticate the user, so he doesn't have to sign in again. Also signing out from the extension will affect both platforms. + + + For browser extensions, we need to define an [authentication trusted origin](https://www.better-auth.com/docs/reference/security#trusted-origins) using an extension scheme. + + Extension schemes (like `chrome-extension://...`) are used for redirecting users to specific screens after authentication and sharing the auth session with the web app. + + To find your extension ID, open Chrome and go to `chrome://extensions/`, enable Developer Mode in the top right, and look for your extension's ID. Then add it to your auth server configuration: + + ```ts title="server.ts" + export const auth = betterAuth({ + ... + + trustedOrigins: ["chrome-extension://your-extension-id"], + + ... + }); + ``` + + Adding your extension scheme to the trusted origins list is crucial for security - it prevents CSRF attacks and blocks malicious open redirects by ensuring only requests from approved origins (your extension) are allowed through. + + [Read more about auth security in Better Auth's documentation.](https://www.better-auth.com/docs/reference/security) + + +## Cookies + +When the user signs in to the [web app](/docs/web) through our [Better Auth API](/docs/web/auth/configuration#api), web app is setting the cookie with the session token under your app's domain, which is later used to validate the session on the server. + +You can find your cookie in *Cookies* tab in the browser's developer tools (remember to be logged in to the app to check it): + +![Session cookie](/images/docs/extension/auth/cookie.png) + +To enable your extension to read the cookie and that way share the session with the web app, you need to set the `cookies` permission in the `wxt.config.ts` under `manifest.permissions` field: + +```ts title="wxt.config.ts" +export default defineConfig({ + manifest: { + permissions: ["cookies"], + }, +}); +``` + +And to be able to read the cookie from your app url, you need to set `host_permissions`, which will include your app url: + +```ts title="wxt.config.ts" +export default defineConfig({ + manifest: { + host_permissions: ["http://localhost/*", "https://your-app-url.com/*"], + }, +}); +``` + +Then you would be able to share the cookie with API requests and also read its value using `browser.cookies` API. + + + Avoid using `` in `host_permissions`. It affects all urls and may cause security issues, as well as a [rejection](https://developer.chrome.com/docs/webstore/review-process#review-time-factors) from the destination store. + + + + + + + + +## Reading session + +You **don't** need to worry about reading, parsing, or validating the session cookie. TurboStarter comes with a pre-built solution that ensures your session is correctly shared with the web app. + +It also ensures that appropriate cookies are passed to [API](/docs/web/api/overview) requests, so you can safely use [protected endpoints](/docs/web/api/protected-routes) (that require authentication) in your extension. + +To get session details in your extension code (e.g., inside a popup window), you can leverage the `useSession` hook provided by the [auth client](https://www.better-auth.com/docs/basic-usage#client-side) (which is also used in the web and mobile apps): + +```tsx title="user.tsx" +import { authClient } from "~/lib/auth"; + +const User = () => { + const { + data: { user, session }, + isPending, + } = authClient.useSession(); + + if (isPending) { + return

Loading...

; + } + + /* do something with the session data... */ + return

{user?.email}

; +}; +``` + +That's how you can access user details right in your extension. + +## Signing out + +Signing out from the extension also involves using the well-known `signOut` function that is derived from our [auth client](https://www.better-auth.com/docs/basic-usage#signout): + +```tsx title="logout.tsx" +import { authClient } from "~/lib/auth"; + +export const Logout = () => { + return ; +}; +``` + +The session is automatically invalidated, so the next use of `useSession` or any other query that depends on the session will return `null`. The UI for both the extension and the web app will be updated to show the user as logged out. + + + As web app is using the same session cookie, the user will be signed out from the web app as well. **This is intentional**, as your extension will most probably serves as an add-on for the web app and it doesn't make sense to keep the user signed in there if the extension is not used. + + +![Sign out](/images/docs/web/auth/sign-out.png) + + +--- +url: /docs/extension/billing +title: Billing +description: Get started with billing in TurboStarter. +--- + +As you could guess, there is no sense in implementing the whole billing process inside the browser extension, so we're relying on the [web app](/docs/web/billing/overview) to handle it. + +> You probably won't display pricing tables inside a popup window, right? + +You can customize the whole flow and onboarding process when a user purchases a plan in your [web app](/docs/web/billing/overview). + +Then you would be able to easily fetch customer data to ensure that the user has access to correct extension features. + +## Fetching customer data + +When your user has purchased a plan from your landing page or web app, you can easily fetch their data using the [API](/docs/extension/api/client). + +To do so, just invoke the `getCustomer` query on the `billing` router: + +```tsx title="customer-screen.tsx" +import { api } from "~/lib/api"; + +export default function CustomerScreen() { + const { data: customer, isLoading } = useQuery({ + queryKey: ["customer"], + queryFn: handle(api.billing.customer.$get), + }); + + if (isLoading) return

Loading...

; + + return

{customer?.plan}

; +} +``` + +You may also want to ensure that user is logged in before fetching their billing data to avoid unnecessary API calls. + +```tsx title="header.tsx" +import { api } from "~/lib/api"; +import { authClient } from "~/lib/auth"; + +export const User = () => { + const { + data: { user }, + } = authClient.useSession(); + + const { data: customer } = useQuery({ + queryKey: ["customer"], + queryFn: handle(api.billing.customer.$get), + enabled: !!user, // [!code highlight] + }); + + if (!user || !customer) { + return null; + } + + return ( +
+

{user.email}

+

{customer.plan}

+
+ ); +}; +``` + +Read more about [auth in extension](/docs/extension/auth/overview). + + +--- +url: /docs/extension/cli +title: CLI +description: Start your new app project with a single command. +--- + + + +To help you get started with TurboStarter **as quickly as possible**, we've developed a [CLI](https://www.npmjs.com/package/@turbostarter/cli) that enables you to create a new project (with all the configuration) in seconds. + +The CLI is a set of commands that will help you create a new project, generate code, and manage your project efficiently. + +Currently, the following action is available: + +* **Starting a new project** - Generate starter code for your project with all necessary configurations in place (billing, database, emails, etc.) + +**The CLI is in beta**, and we're actively working on adding more commands and actions. Soon, the following features will be available: + +* **Translations** - Translate your project, verify translations, and manage them effectively +* **Installing plugins** - Easily install plugins for your project +* **Dynamic code generation** - Generate dynamic code based on your project structure + +## Installation + +You can run commands using `npx`: + +```bash +npx turbostarter +npx @turbostarter/cli@latest +``` + + + If you don't want to install the CLI globally, you can simply replace the examples below with `npx @turbostarter/cli@latest` instead of `turbostarter`. + + This also allows you to always run the latest version of the CLI without having to update it. + + +## Usage + +Running the CLI without any arguments will display the general information about the CLI: + +```bash +Usage: turbostarter [options] [command] + +Your Turbo Assistant for starting new projects, adding plugins and more. + +Options: + -v, --version display the version number + -h, --help display help for command + +Commands: + new create a new TurboStarter project + help [command] display help for command +``` + +You can also display help for it or check the actual version. + +### Starting a new project + +Use the `new` command to initialize configuration and dependencies for a new project. + +```bash +npx turbostarter new +``` + +You will be asked a few questions to configure your project: + +```bash +✔ All prerequisites are satisfied, let's start! 🚀 + +? What do you want to ship? › + ◉ Web app + ◉ Mobile app + ◯ Browser extension +? Enter your project name. › +? How do you want to use database? › + Local (powered by Docker) + Cloud +? What do you want to use for billing? › + Stripe + Lemon Squeezy + +... + +🎉 You can now get started. Open the project and just ship it! 🎉 + +Problems? https://www.turbostarter.dev/docs +``` + +It will create a new project, configure providers, install dependencies and start required services in development mode. + + +--- +url: /docs/extension/configuration/app +title: App configuration +description: Learn how to setup the overall settings of your extension. +--- + +The application configuration is set at `apps/extension/src/config/app.ts`. This configuration stores some overall variables for your application. + +This allows you to host multiple apps in the same monorepo, as every application defines its own configuration. + +The recommendation is to **not update this directly** - instead, please define the environment variables and override the default behavior. The configuration is strongly typed so you can use it safely accross your codebase - it'll be validated at build time. + +```ts title="apps/extension/src/config/app.ts" +import env from "env.config"; + +export const appConfig = { + name: env.VITE_PRODUCT_NAME, + url: env.VITE_SITE_URL, + locale: env.VITE_DEFAULT_LOCALE, + theme: { + mode: env.VITE_THEME_MODE, + color: env.VITE_THEME_COLOR, + }, +} as const; +``` + +For example, to set the extension default theme color, you'd update the following variable: + +```dotenv title=".env.local" +VITE_THEME_COLOR="yellow" +``` + + + Do NOT use `process.env` to get the values of the variables. Variables + accessed this way are not validated at build time, and thus the wrong variable + can be used in production. + + +## WXT config + +To configure framework-specific settings, you can use the `wxt.config.ts` file. You can configure a lot of options there, such as [manifest](/docs/extension/configuration/manifest), [project structure](https://wxt.dev/guide/essentials/project-structure.html) or even [underlying Vite config](https://wxt.dev/guide/essentials/config/vite.html): + +```ts title="wxt.config.ts" +import { defineConfig } from "wxt"; + +export default defineConfig({ + srcDir: "src", + entrypointsDir: "app", + outDir: "build", + modules: [], + manifest: { + // Put manifest changes here + }, + vite: () => ({ + // Override config here, same as `defineConfig({ ... })` + // inside vite.config.ts files + }), +}); +``` + +Make sure to setup it correctly, as it's the main source of config for your development, build and publishing process. + + +--- +url: /docs/extension/configuration/environment-variables +title: Environment variables +description: Learn how to configure environment variables. +--- + +Environment variables are defined in the `.env` file in the root of the repository and in the root of the `apps/extension` package. + +* **Shared environment variables**: Defined in the **root** `.env` file. These are shared between environments (e.g., development, staging, production) and apps (e.g., web, extension). +* **Environment-specific variables**: Defined in `.env.development` and `.env.production` files. These are specific to the development and production environments. +* **App-specific variables**: Defined in the app-specific directory (e.g., `apps/extension`). These are specific to the app and are not shared between apps. +* **Bundle-specific variables**: Specific to the [bundle target](https://wxt.dev/guide/essentials/config/environment-variables.html#built-in-environment-variables) (e.g. `.env.safari`, `.env.firefox`) or [bundle tag](https://wxt.dev/guide/essentials/config/environment-variables.html#built-in-environment-variables) (e.g. `.env.testing`) +* **Build environment variables**: Not stored in the `.env` file. Instead, they are stored in the environment variables of the CI/CD system. +* **Secret keys**: They're not stored on the extension side, instead [they're defined on the web side.](/docs/web/configuration/environment-variables#secret-keys) + +## Shared variables + +Here you can add all the environment variables that are shared across all the apps. + +To override these variables in a specific environment, please add them to the specific environment file (e.g. `.env.development`, `.env.production`). + +```dotenv title=".env.local" +# Shared environment variables + +# The database URL is used to connect to your database. +DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres" + +# The name of the product. This is used in various places across the apps. +PRODUCT_NAME="TurboStarter" + +# The url of the web app. Used mostly to link between apps. +URL="http://localhost:3000" + +... +``` + +## App-specific variables + +Here you can add all the environment variables that are specific to the app (e.g. `apps/extension`). + +You can also override the shared variables defined in the root `.env` file. + +```dotenv title="apps/extension/.env.local" +# App-specific environment variables + +# Env variables extracted from shared to be exposed to the client in WXT (Vite) extension +VITE_SITE_URL="${URL}" +VITE_DEFAULT_LOCALE="${DEFAULT_LOCALE}" + +# Theme mode and color +VITE_THEME_MODE="system" +VITE_THEME_COLOR="orange" + +... +``` + + + To make environment variables available in the browser extension code, you need to prefix them with `VITE_`. They will be injected to the code during the build process. + + Only environment variables prefixed with `VITE_` will be injected. + + [Read more about Vite environment variables.](https://vite.dev/guide/env-and-mode.html#env-files) + + +## Bundle-specific variables + +WXT also provides environment variables specific to a certain [build target](https://wxt.dev/guide/essentials/config/environment-variables.html#built-in-environment-variables) or [build tag](https://wxt.dev/guide/essentials/config/environment-variables.html#built-in-environment-variables) when creating the final bundle. Given the following build command: + +```json title="package.json" +"scripts": { + "build": "wxt build -b firefox --mode testing" +} +``` + +The following env files will be considered, ordered by priority: + +* `.env.firefox` +* `.env.testing` +* `.env` + +You shouldn't worry much about this, as TurboStarter comes with already configured build processes for all the major browsers. + +## Build environment variables + +To allow your extension to build properly on CI you need to define your environment variables on your CI/CD system (e.g. [Github Actions](https://docs.github.com/en/actions/learn-github-actions/environment-variables)). + +TurboStarter comes with predefined Github Actions workflow used to build and submit your extension to the stores. It's located in `.github/workflows/publish-extension.yml` file. + +To correctly set up the build environment variables, you need to define them under `env` section and then add them as a [secrets](http://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions) to your repository. + +```yaml title="publish-extension.yml" +... + +jobs: + extension: + name: 🚀 Publish extension + runs-on: ubuntu-latest + environment: Production + env: + VITE_SITE_URL: ${{ secrets.SITE_URL }} + + ... + +``` + +We'll go through the whole process of building and publishing the extension in the [publishing guide](/docs/extension/publishing/checklist). + +## Secret keys + +Secret keys and sensitive information are to be **never** stored on the extension app code. + + + It means that you will need to add the secret keys to the **web app, where the API is deployed.** + + The browser extension should only communicate with the backend API, which is typically part of the web app. The web app is responsible for handling sensitive operations and storing secret keys securely. + + [See web documentation for more details.](/docs/web/configuration/environment-variables#secret-keys) + + This is not a TurboStarter-specific requirement, but a best practice for security for any + application. Ultimately, it's your choice. + + + +--- +url: /docs/extension/configuration/manifest +title: Manifest +description: Learn how to configure the manifest of your extension. +--- + +As a requirement from web stores, every extension must have a `manifest.json` file in its root directory that lists important information about the structure and behavior of that extension. + +It's a JSON file that contains metadata about the extension, such as its name, version, and permissions. + +You can read more about it in the [official documentation](https://developer.chrome.com/docs/extensions/reference/manifest). + +## Where is the `manifest.json` file? + +WXT **abstracts away** the manifest file. The framework generates the manifest under the hood based on your source files and configurations you export from your code, similar to how Next.js abstracts page routing and SSG with the file system and page components. + +That way, you don't have to manually create the `manifest.json` file and worry about correctly setting all the fields. + +Most of the common properties are taken from the `package.json` and `wxt.config.ts` files: + +| Manifest Field | Abstractions | +| ------------------------ | ------------------------------------------------------------- | +| icons | Auto generated with the `icon.png` in the `/assets` directory | +| action, browser\_actions | Popup window | +| options\_ui | Options page | +| content\_scripts | Content scripts | +| background | Background service worker | +| version | set by the `version` field in `package.json` | +| name | set by the `name` field in `wxt.config.ts` | +| description | set by the `description` field in `wxt.config.ts` | +| author | set by the `author` field in `wxt.config.ts` | +| homepage\_url | set by the `homepage` field in `wxt.config.ts` | + +WXT build process centralizes common metadata and resolves any static file references (such as popup, background, content scripts, and so on) automatically. + +This enables you to focus on the metadata that matters, such as name, description, OAuth, and so on. + +## Overriding manifest + +Sometimes, you want to override the default manifest fields (e.g. because you need to add a new permission that is required for your extension to work). + +You'll need to modify your project's `wxt.config.ts` like so: + +```ts title="apps/extension/wxt.config.ts" +export default defineConfig({ + manifest: { + permissions: ["activeTab"], + }, +}); +``` + +Then, your settings will be merged with the settings auto-generated by WXT. + +### Environment variables + +You can use environment variables inside the manifest overrides: + +```ts title="apps/extension/wxt.config.ts" +export default defineConfig({ + manifest: { + browser_specific_settings: { + gecko: { + id: import.meta.env.VITE_FIREFOX_EXT_ID, + }, + }, + }, +}); +``` + +If the environment variable could not be found, the field will be removed completely from the manifest. + +### Locales + +TurboStarter extension supports [extension localization](https://developer.chrome.com/docs/extensions/reference/api/i18n) out-of-the-box. You can customize e.g. your extension's name and description based on the language of the user's browser. + +Locales are defined in the `/public/_locales` directory. The directory should contain a `messages.json` file for each language you want to support (e.g. `/public/_locales/en/messages.json` and `/public/_locales/es/messages.json`). + +By default, the first locale alphabetically available is used as default. However, you can specify a `default_locale` in your manifest like so: + +```ts title="apps/extension/wxt.config.ts" +export default defineConfig({ + manifest: { + default_locale: "en", + }, +}); +``` + +To reference a locale string inside your manifest overrides, wrap the key inside `__MSG___`: + +```ts title="apps/extension/wxt.config.ts" +export default defineConfig({ + manifest: { + name: "__MSG_extensionName__", + description: "__MSG_extensionDescription__", + }, +}); +``` + +Apart of this, we also configure [in-extension internationalization](/docs/extension/internationalization) out-of-the-box to easily translate your components and views. + + + + + + + + + + + + +--- +url: /docs/extension/customization/add-app +title: Adding apps +description: Learn how to add apps to your Turborepo workspace. +--- + + + This is an **advanced topic** - you should only follow these instructions if you are sure you want to add a new app to your TurboStarter project within your monorepo and want to keep pulling updates from the TurboStarter repository. + + +In some ways - creating a new repository may be the easiest way to manage your application. However, if you want to keep your application within the monorepo and pull updates from the TurboStarter repository, you can follow these instructions. + +To pull updates into a separate application outside of `extension` - we can use [git subtree](https://www.atlassian.com/git/tutorials/git-subtree). + +Basically, we will create a subtree at `apps/extension` and create a new remote branch for the subtree. When we create a new application, we will pull the subtree into the new application. This allows us to keep it in sync with the `apps/extension` folder. + +To add a new app to your TurboStarter project, you need to follow these steps: + + + + ## Create a subtree + + First, we need to create a subtree for the `apps/extension` folder. We will create a branch named `extension-branch` and create a subtree for the `apps/extension` folder. + + ```bash + git subtree split --prefix=apps/extension --branch extension-branch + ``` + + + + ## Create a new app + + Now, we can create a new application in the `apps` folder. + + Let's say we want to create a new app `ai-chat` at `apps/ai-chat` with the same structure as the `apps/extension` folder (which acts as the template for all new apps). + + ```bash + git subtree add --prefix=apps/ai-chat origin extension-branch --squash + ``` + + You should now be able to see the `apps/ai-chat` folder with the contents of the `apps/extension` folder. + + + + ## Update the app + + When you want to update the new application, follow these steps: + + ### Pull the latest updates from the TurboStarter repository + + The command below will update all the changes from the TurboStarter repository: + + ```bash + git pull upstream main + ``` + + ### Push the `extension-branch` updates + + After you have pulled the updates from the TurboStarter repository, you can split the branch again and push the updates to the extension-branch: + + ```bash + git subtree split --prefix=apps/extension --branch extension-branch + ``` + + Now, you can push the updates to the `extension-branch`: + + ```bash + git push origin extension-branch + ``` + + ### Pull the updates to the new application + + Now, you can pull the updates to the new application: + + ```bash + git subtree pull --prefix=apps/ai-chat origin extension-branch --squash + ``` + + + +That's it! You now have a new application in the monorepo 🎉 + + +--- +url: /docs/extension/customization/add-package +title: Adding packages +description: Learn how to add packages to your Turborepo workspace. +--- + + + This is an **advanced topic** - you should only follow these instructions if you are sure you want to add a new package to your TurboStarter application instead of adding a folder to your application in `apps/web` or modify existing packages under `packages`. You don't need to do this to add a new page or component to your application. + + +To add a new package to your TurboStarter application, you need to follow these steps: + + + + ## Generate a new package + + First, enter the command below to create a new package in your TurboStarter application: + + ```bash + turbo gen package + ``` + + Turborepo will ask you to enter the name of the package you want to create. Enter the name of the package you want to create and press enter. + + If you don't want to add dependencies to your package, you can skip this step by pressing enter. + + The command will have generated a new package under packages named `@turbostarter/`. If you named it `example`, the package will be named `@turbostarter/example`. + + + + ## Export a module from your package + + By default, the package exports a single module using the `index.ts` file. You can add more exports by creating new files in the package directory and exporting them from the `index.ts` file or creating export files in the package directory and adding them to the `exports` field in the `package.json` file. + + ### From `index.ts` file + + The easiest way to export a module from a package is to create a new file in the package directory and export it from the `index.ts` file. + + ```ts title="packages/example/src/module.ts" + export function example() { + return "example"; + } + ``` + + Then, export the module from the `index.ts` file. + + ```ts title="packages/example/src/index.ts" + export * from "./module"; + ``` + + ### From `exports` field in `package.json` + + **This can be very useful for tree-shaking.** Assuming you have a file named `module.ts` in the package directory, you can export it by adding it to the `exports` field in the `package.json` file. + + ```json title="packages/example/package.json" + { + "exports": { + ".": "./src/index.ts", + "./module": "./src/module.ts" + } + } + ``` + + **When to do this?** + + 1. when exporting two modules that don't share dependencies to ensure better tree-shaking. For example, if your exports contains both client and server modules. + 2. for better organization of your package + + For example, create two exports `client` and `server` in the package directory and add them to the `exports` field in the `package.json` file. + + ```json title="packages/example/package.json" + { + "exports": { + ".": "./src/index.ts", + "./client": "./src/client.ts", + "./server": "./src/server.ts" + } + } + ``` + + 1. The `client` module can be imported using `import { client } from '@turbostarter/example/client'` + 2. The `server` module can be imported using `import { server } from '@turbostarter/example/server'` + + + + ## Use the package in your extension + + You can now use the package in your extension by importing it using the package name: + + ```ts title="app/popup/index.tsx" + import { example } from "@turbostarter/example"; + + console.log(example()); + ``` + + + +Et voilà! You have successfully added a new package to your TurboStarter extension. 🎉 + + +--- +url: /docs/extension/customization/components +title: Components +description: Manage and customize your extension components. +--- + +For the components part, we're using [shadcn/ui](https://ui.shadcn.com) for atomic, accessible and highly customizable components. + + + shadcn/ui is a powerful tool that allows you to generate pre-designed + components with a single command. It's built with Tailwind CSS and Radix UI, + and it's highly customizable. + + +TurboStarter defines two packages that are responsible for the UI part of your app: + +* `@turbostarter/ui` - shared styles, [themes](/docs/web/customization/styling#themes) and assets (e.g. icons) +* `@turbostarter/ui-web` - pre-built UI web components, ready to use in your app + +## Adding a new component + +There are basically two ways to add a new component: + + + + TurboStarter is fully compatible with [shadcn CLI](https://ui.shadcn.com/docs/cli), so you can generate new components with single command. + + Run the following command from the **root** of your project: + + ```bash + pnpm --filter @turbostarter/ui-web ui:add + ``` + + This will launch an interactive command-line interface to guide you through the process of adding a new component where you can pick which component you want to add. + + ```bash + Which components would you like to add? > Space to select. A to toggle all. + Enter to submit. + + ◯ accordion + ◯ alert + ◯ alert-dialog + ◯ aspect-ratio + ◯ avatar + ◯ badge + ◯ button + ◯ calendar + ◯ card + ◯ checkbox + ``` + + Newly created components will appear in the `packages/ui/web/src` directory. + + + + You can always copy-paste a component from the [shadcn/ui](https://ui.shadcn.com/docs/components) website and modify it to your needs. + + This is possible, because the components are headless and don't need (in most cases) any additional dependencies. + + Copy code from the website, create a new file in the `packages/ui/web/src` directory and paste the code into the file. + + + + + Keep in mind that you should always try to keep shared components as atomic as possible. This will make it easier to reuse them and to build specific views by composition. + + E.g. include components like `Button`, `Input`, `Card`, `Dialog` in shared package, but keep specific components like `LoginForm` in your app directory. + + +## Using components + +Each component is a standalone entity which has a separate export from the package. It helps to keep things modular, avoid unnecessary dependencies and make tree-shaking possible. + +To import a component from the UI package, use the following syntax: + +```tsx title="components/my-component.tsx" +// [!code word:card] +import { + Card, + CardContent, + CardHeader, + CardFooter, + CardTitle, + CardDescription, +} from "@turbostarter/ui-web/card"; +``` + +Then you can use it to build a component specific to your app: + +```tsx title="components/my-component.tsx" +export function MyComponent() { + return ( + + + My Component + + +

My Component Content

+
+ + + +
+ ); +} +``` + + + We recommend using [v0](https://v0.dev) to generate layouts for your app. It's a powerful tool that allows you to generate layouts from the natural language instructions. + + Of course, **it won't replace a designer**, but it can be a good starting point for your layout. + + + + + + + + + +--- +url: /docs/extension/customization/styling +title: Styling +description: Get started with styling your extension. +--- + +To build the extension interface TurboStarter comes with [Tailwind CSS](https://tailwindcss.com/) and [Radix UI](https://www.radix-ui.com/) pre-configured. + + + The combination of Tailwind CSS and Radix UI gives ready-to-use, accessible UI components that can be fully customized to match your brands design. + + +## Tailwind configuration + +In the `packages/ui/shared/src/styles` directory, you will find shared CSS files with Tailwind CSS configuration. To change global styles, you can edit the files in this folder. + +Here is an example of a shared CSS file that includes the Tailwind CSS configuration: + +```css title="packages/ui/shared/src/styles/globals.css" +@import "tailwindcss"; +@import "./themes.css"; + +@custom-variant dark (&:is(.dark *)); + +:root { + --radius: 0.65rem; +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + + ... +} +``` + +For colors, we rely strictly on [CSS Variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties) in [OKLCH](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch) format to allow for easy theme management without the need for any JavaScript. + +Also, each app has its own `globals.css` file, which extends the shared config and allows you to override the global styles. + +Here is an example of an extension's `globals.css` file: + +```css title="apps/extension/src/assets/styles/globals.css" +@import url("https://fonts.googleapis.com/css2?family=Geist+Mono:wght@100..900&family=Geist:wght@100..900&display=swap"); +@import "@turbostarter/ui/globals.css"; +@import "@turbostarter/ui-web/globals.css"; + +@theme { + --font-sans: "Geist", sans-serif; + --font-mono: "Geist Mono", monospace; +} +``` + +This way, we maintain a separation of concerns and a clear structure for the Tailwind CSS configuration. + +## Themes + +TurboStarter comes with **9+** predefined themes, which you can use to quickly change the look and feel of your app. + +They're defined in the `packages/ui/shared/src/styles/themes` directory. Each theme is a set of variables that can be overridden: + +```ts title="packages/ui/shared/src/styles/themes/colors/orange.ts" +export const orange = { + light: { + background: [1, 0, 0], + foreground: [0.141, 0.005, 285.823], + card: [1, 0, 0], + "card-foreground": [0.141, 0.005, 285.823], + ... + } +} satisfies ThemeColors; +``` + +Each variable is stored as a [OKLCH](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch) array, which is then converted to a CSS variable at build time (by our custom build script). That way we can ensure full type-safety and reuse themes across different parts of our apps (e.g. use the same theme in emails). + +Feel free to add your own themes or override the existing ones to match your brand's identity. + +To apply a theme to your app, you can use the `data-theme` attribute on your layout wrapper for each part of the extension: + +```tsx title="modules/common/layout/layout.tsx" +import { StorageKey, useStorage } from "~/lib/storage"; + +export const Layout = ({ children }: { children: React.ReactNode }) => { + const { data } = useStorage(StorageKey.THEME); + + return ( +
+ {children} +
+ ); +}; +``` + +In TurboStarter, we're using [Storage API](/docs/extension/structure/storage) to persist the user's theme selection and then apply it to the `div#main` element. + +## Dark mode + +The starter kit comes with a built-in dark mode support. + +Each theme has a corresponding dark mode variables which are used to change the theme to its dark mode counterpart. + +```ts title="packages/ui/shared/src/styles/themes/colors/orange.ts" +export const orange = { + light: {}, + dark: { + background: [0.141, 0.005, 285.823], + foreground: [0.985, 0, 0], + card: [0.21, 0.006, 285.885], + "card-foreground": [0.985, 0, 0], + ... + } +} satisfies ThemeColors; +``` + +Because the dark variant is defined to use a class (`@custom-variant dark (&:is(.dark *))`) in the shared Tailwind configuration, we need to add the `dark` class to the root element to apply dark mode styles. + +The same as for the theme color, we're using here the [Storage API](/docs/extension/structure/storage) to persist the user's dark mode selection and then apply correct class name to the root `div` element: + +```tsx title="modules/common/layout/layout.tsx" +import { StorageKey, useStorage } from "~/lib/storage"; + +export const Layout = ({ children }: { children: React.ReactNode }) => { + const { data } = useStorage(StorageKey.THEME); + + return ( +
+ {children} +
+ ); +}; +``` + +You can also define the default theme mode and color in the [app configuration](/docs/extension/configuration/app). + + + + + + + + +--- +url: /docs/extension/database +title: Database +description: Get started with the database. +--- + + + To enable communication between your WXT extension and the server in a production environment, the web application with Hono API must be deployed first. + + + + + + + + +As browser extensions use only client-side code, **there's no way to interact with the database directly**. + +Also, you should avoid any workarounds to interact with the database directly, because it can lead to leaking your database credentials and other security issues. + +## Recommended approach + +You can safely use the [API](/docs/extension/api/overview) and invoke procedures which will run queries on the database. + +To do this you need to set up the database on the [web, server side](/docs/web/database/overview) and then use the [API client](/docs/extension/api/client) to interact with it. + +Learn more about its configuration in the web part of the docs, especially in the following sections: + + + + + + + + + + + + +--- +url: /docs/extension/extras +title: Extras +description: See what you get together with the code. +--- + +## Tips and Tricks + +In many places, next to the code you will find some marketing tips, design suggestions, and potential risks. This is to help you build a better product and avoid common pitfalls. + +```tsx title="Hero.tsx" +return ( +
+ {/* 💡 Use something that user can visualize e.g. + "Ship your startup while on the toilet" */} +

Best startup on the world

+
+); +``` + +### Submission tips + +When it comes to mobile app and browser extension, you must submit your product to review from Apple/Google etc. We have some tips for you to make sure your submission goes smoothly. + +```json title="app.json" +{ + "ios": { + "infoPlist": { + /* 🍎 add descriptive justification of using this permission on iOS */ + "NSCameraUsageDescription": "This app uses the camera to scan barcodes on event tickets." + } + } +} +``` + +As well as providing you with the info on how to make your store listings better: + +```json title="package.json" +{ + "manifest": { + /* 💡 Use localized messages to get more visibility in web stores */ + "name": "__MSG_extensionName__", + "default_locale": "en" + } +} +``` + +## Discord community + +We have a Discord community where you can ask questions and share your projects. It's a great place to get help and meet other developers. Check more details at [/discord](/discord). + + + +![Discord](/images/docs/discord.png) + +## 25+ SaaS Ideas + +Not sure what to build? We have a list of **25+** SaaS ideas that you can use to get started 🔥 + +Grouped by category, these ideas are a great way to get inspired and start building your next project. + +Including design, copies, marketing tips and potential risks, this list is a great resource for anyone looking to build a SaaS product. + +![SaaS Ideas](/images/docs/saas-ideas.png) + + +--- +url: /docs/extension/faq +title: FAQ +description: Find answers to common technical questions. +--- + +## Why isn't everything hidden and configured with one BIG config file? + +TurboStarter intentionally exposes the underlying code rather than hiding it behind configuration files (like some starters do). This design choice follows our **you own your code** philosophy, giving you full control and flexibility over your codebase. + +While a single config file might seem simpler initially, it often becomes restrictive when you need to customize functionality beyond what the config allows. With direct access to the code, you can modify any part of the system to match your specific requirements. + +## I don't know some technology! Should I buy TurboStarter? + +You should be prepared for a learning curve or consider learning it first. However, TurboStarter will still work for you if you're willing to learn. + +Even without knowing some technologies, you can still use the rest of the features. + +## I don't need mobile app or browser extension, what should I do? + +You can simply ignore the mobile app and browser extension parts of the project. You can remove the `apps/mobile` and `apps/extension` directories from the project. + +The modular nature of TurboStarter allows you to remove parts of the project that you don't need without affecting the rest of the stack. + +## I want to use a different provider for X + +Sure! TurboStarter is designed to be modular, so configuring new provider (e.g. for emails, billing or any other service) is straightforward. You just need to make sure your configuration is compatible with common interface to be able to plug it into the codebase. + +## Will you add more packages in the future? + +Yes, we will keep updating TurboStarter with new packages and features. This kit is designed to be modular, allowing for new features and packages to be added without interfering with your existing code. You can always [update your project](/docs/web/installation/update) to the latest version. + +## Can I use this kit for a non-SaaS project? + +This kit is mainly designed for SaaS projects. If you're building something other than a SaaS, the Next.js SaaS Boilerplate might include features you don't need. You can still use it for non-SaaS projects, but you may need to remove or modify features that are specific to SaaS use cases. + +## Can I use personal accounts only? + +Yes! You can disable team accounts and have personal accounts only by setting a feature flag. + +## Does it set up the production instance for me? + +No, TurboStarter does not set up the production instance for you. This includes setting up databases, Stripe, or any other services you need. TurboStarter does not have access to your Stripe or Resend accounts, so setup on your end is required. TurboStarter provides the codebase and documentation to help you set up your SaaS project. + +## Does the starter include Solito? + +No. Solito will not be included in this repo. It is a great tool if you want to share code between your Next.js and Expo app. However, the main purpose of this repo is not the integration between Next.js and Expo — it's the code splitting of your SaaS platforms into a monorepo. You can utilize the monorepo with multiple apps, and it can be any app such as Vite, Electron, etc. + +Integrating Solito into this repo isn't hard, and there are a few [official templates](https://github.com/nandorojo/solito/tree/master/example-monorepos) by the creators of Solito that you can use as a reference. + +## Does this pattern leak backend code to my client applications? + +No, it does not. The `api` package should only be a production dependency in the Next.js application where it's served. The Expo app, browser extension, and all other apps you may add in the future should only add the `api` package as a dev dependency. This lets you have full type safety in your client applications while keeping your backend code safe. + +If you need to share runtime code between the client and server, you can create a separate `shared` package for this and import it on both sides. + +## How do I get support if I encounter issues? + +For support, you can: + +1. Visit our [Discord](https://discord.gg/KjpK2uk3JP) +2. Contact us via support email ([hello@turbostarter.dev](mailto:hello@turbostarter.dev)) + +## Are there any example projects or demos? + +Yes - feel free to check out our demo app at [demo.turbostarter.dev](https://demo.turbostarter.dev). Also, you can get inspired by projects built by our customers - take a look at [Showcase](/#showcase). + +## How do I deploy my application? + +Please check the [production checklist](/docs/web/deployment/checklist) for more information. + +## How do I update my project when a new version of the boilerplate is released? + +Please read the [documentation for updating your TurboStarter code](/docs/web/installation/update). + +## Can I use the React package X with this kit? + +Yes, you can use any React package with this kit. The kit is based on React, so you are generally only constrained by the underlying technologies and not by the kit itself. Since you own and can edit all the code, you can adapt the kit to your needs. However, if there are limitations with the underlying technology, you might need to work around them. + +## Can I integrate TurboStarter into an existing project? + +TurboStarter is a full-stack starter intended to be used as the foundation of your app. You can copy individual modules or patterns into an existing codebase, but retrofitting the entire starter into a mature project is typically not recommended and is not officially supported. If you choose to copy parts, prefer isolating boundaries (e.g., `packages/` modules) and aligning interfaces first. + +## Where can I deploy my application? + +TurboStarter targets modern Node.js/Next.js runtimes. You can deploy to providers that support these environments, such as [Vercel](/docs/web/deployment/vercel), [Railway](/docs/web/deployment/railway), [Render](/docs/web/deployment/render), [Fly](/docs/web/deployment/fly), or [Netlify](/docs/web/deployment/netlify) - following their Next.js guidance. Review our [production checklist](/docs/web/deployment/checklist) before going live. + +## Can I easily swap providers (billing, email, etc.)? + +Yes. The starter organizes integrations behind clear interfaces so you can replace providers (e.g., billing or email) with minimal surface changes. Keep your implementation behind a module boundary and adapt to the existing types to avoid ripple effects. + + +--- +url: /docs/extension +title: Introduction +description: Get started with TurboStarter extension kit. +--- + +Welcome to the TurboStarter documentation. This is your starting point for learning about the starter kit, its structure, features, and how to use it for your app development. + + + +## What is TurboStarter? + +TurboStarter is a fullstack starter kit that helps you build scalable and production-ready web apps, mobile apps, and browser extensions in minutes. + +Looking to bootstrap your project quickly? Check out our [TurboStarter CLI guide](/blog/the-only-turbo-cli-you-need-to-start-your-next-project-in-seconds) to get started in seconds. + +## Demo apps + +TurboStarter provides a suite of live demo applications you can try instantly - right in your browser, on your phone, or via browser extensions. Try them live by clicking the buttons below. + + + +## Principles + +TurboStarter is built with the following principles: + +* **As simple as possible** - It should be easy to understand, easy to use, and strongly avoid overengineering things. +* **As few dependencies as possible** - It should have as few dependencies as possible to allow you to take full control over every part of the project. +* **As performant as possible** - It should be fast and light without any unnecessary overhead. + +## Features + +Before diving into the technical details, let's overview the features TurboStarter provides. + +### Multi-platform development + +* [Web](/docs/web/stack): Build web apps with React, Next.js, and Tailwind CSS. +* [Mobile](/docs/mobile/stack): Build mobile apps with React Native and Expo. +* [Browser extension](/docs/extension/stack): Build browser extensions with React and WXT. + +For those interested in AI development, check out our dedicated [TurboStarter AI documentation](/ai/docs) with specialized features for building AI-powered applications. + + + Most features are available on all platforms. You can use the **same codebase** to build web, mobile, and browser extension apps. + + +### Authentication + + + + + + + + + + + + + + + + + + + +### Organizations/teams + + + + + + + + + + + +### Billing + + + + + + + + + + + + + + + +### Database + + + + + + + + + + + +### API + + + + + + + + + + + + + +### Admin + + + + + + + + + + + +### AI + + + + Seamless integration of OpenAI, Anthropic, Groq, Mistral, and Gemini. For more advanced AI features, check out [TurboStarter AI](/ai/docs). + + + + + + + + + +### Internationalization + + + + + + + + + + + +### Emails + + + + + + + + + + + +### Landing page + + + + + + + + + + + + + + + +### Marketing + + + + + + + + + + + + + + + + + +### Storage + + + + + + + +### CMS + + + + + + + +### Theming + + + + + + + + + + + +### Analytics + + + + + + + + + + + +### Monitoring + + + + + + + + + + + +### Deployment + + + + + + + + + + + +### Testing + + + + + + + + + + + +## Use like LEGO blocks + +The biggest advantage of TurboStarter is its modularity. You can use the entire stack or just the parts you need. It's like LEGO blocks - you can build anything you want with it. + +If you don't need a specific feature, feel free to remove it without affecting the rest of the stack. + +This approach allows for: + +* **Easy feature integration** - plug new features into the kit with minimal changes. +* **Simplified maintenance** - keep the codebase clean and maintainable. +* **Core feature separation** - distinguish between core features and custom features. +* **Additional modules** - easily add modules like billing, CMS, monitoring, logger, mailer, and more. + +## Scope of this documentation + +While building a SaaS application involves many moving parts, this documentation focuses specifically on TurboStarter. For in-depth information on the underlying technologies, please refer to their respective official documentation. + +This documentation will guide you through configuring, running, and deploying the kit, and will provide helpful links to the official documentation of technologies where necessary. + +## `llms.txt` + +You can access the entire TurboStarter documentation in Markdown format at [/llms.txt](/llms.txt). This can be used to ask any LLM (assuming it has a big enough context window) questions about the TurboStarter based on the most up-to-date documentation. + +### Example usage + +For instance, to prompt an LLM with questions about the TurboStarter: + +1. Copy the documentation contents from [/llms.txt](/llms.txt) +2. Use the following prompt format: + +``` +Documentation: +{paste documentation here} +--- +Based on the above documentation, answer the following: +{your question} +``` + +## Enjoy! + +This documentation is designed to be easy to follow and understand. If you have any questions or need help, feel free to reach out to us at [hello@turbostarter.dev](mailto:hello@turbostarter.dev). + +Explore new features, build amazing apps, and have fun! 🚀 + + +--- +url: /docs/extension/installation/clone +title: Cloning repository +description: Get the code to your local machine and start developing your extension. +--- + + + Ensure you have Git installed on your local machine before proceeding. You can download Git from [here](https://git-scm.com). + + +## Git clone + +Clone the repository using the following command: + +```bash +git clone git@github.com:turbostarter/core +``` + +By default, we're using [SSH](https://docs.github.com/en/authentication/connecting-to-github-with-ssh) for all Git commands. If you don't have it configured, please refer to the [official documentation](https://docs.github.com/en/authentication/connecting-to-github-with-ssh) to set it up. + +Alternatively, you can use HTTPS to clone the repository: + +```bash +git clone https://github.com/turbostarter/core +``` + +Another alternative could be to use the [Github CLI](https://cli.github.com/) or [Github Desktop](https://desktop.github.com/) for Git operations. + + + +## Git remote + +After cloning the repository, remove the original origin remote: + +```bash +git remote rm origin +``` + +Add the upstream remote pointing to the original repository to pull updates: + +```bash +git remote add upstream git@github.com:turbostarter/core +``` + +Once you have your own repository set up, add your repository as the origin: + +```bash +git remote add origin +``` + + + +## Staying up to date + +To pull updates from the upstream repository, run the following command daily (preferably with your morning coffee ☕): + +```bash +git pull upstream main +``` + +This ensures your repository stays up to date with the latest changes. + +Check [Updating codebase](/docs/web/installation/update) for more details on updating your codebase. + + +--- +url: /docs/extension/installation/commands +title: Common commands +description: Learn about common commands you need to know to work with the extension project. +--- + + + For sure, you don't need these commands to kickstart your project, but it's useful to know they exist for when you need them. + + + + You can set up aliases for these commands in your shell configuration file. For example, you can set up an alias for `pnpm` to `p`: + + ```bash title="~/.bashrc" + alias p='pnpm' + ``` + + Or, if you're using [Zsh](https://ohmyz.sh/), you can add the alias to `~/.zshrc`: + + ```bash title="~/.zshrc" + alias p='pnpm' + ``` + + Then run `source ~/.bashrc` or `source ~/.zshrc` to apply the changes. + + You can now use `p` instead of `pnpm` in your terminal. For example, `p i` instead of `pnpm install`. + + + + To inject environment variables into the command you run, prefix it with `with-env`: + + ```bash + pnpm with-env + ``` + + For example, `pnpm with-env pnpm build` will run `pnpm build` with the environment variables injected. + + Some commands, like `pnpm dev`, automatically inject the environment variables for you. + + +## Installing dependencies + +To install the dependencies, run: + +```bash +pnpm install +``` + +## Starting development server + +Start development server by running: + +```bash +pnpm dev +``` + +## Building project + +To build the project (including all apps and packages), run: + +```bash +pnpm build +``` + +## Building specific app/package + +To build a specific app/package, run: + +```bash +pnpm turbo build --filter= +``` + +## Cleaning project + +To clean the project, run: + +```bash +pnpm clean +``` + +Then, reinstall the dependencies: + +```bash +pnpm install +``` + +## Formatting code + +To check for formatting errors using Prettier, run: + +```bash +pnpm format +``` + +To fix formatting errors using Prettier, run: + +```bash +pnpm format:fix +``` + +## Linting code + +To check for linting errors using ESLint, run: + +```bash +pnpm lint +``` + +To fix linting errors using ESLint, run: + +```bash +pnpm lint:fix +``` + +## Typechecking + +To typecheck the code using TypeScript for any type errors, run: + +```bash +pnpm typecheck +``` + +## Adding UI components + + + + To add a new web component, run: + + ```bash + pnpm --filter @turbostarter/ui-web ui:add + ``` + + This command will add and export a new component to `@turbostarter/ui-web` package. + + + + To add a new mobile component, run: + + ```bash + pnpm --filter @turbostarter/ui-mobile ui:add + ``` + + This command will add and export a new component to `@turbostarter/ui-mobile` package. + + + +## Services commands + + + To run the services containers locally, you need to have [Docker](https://www.docker.com/) installed on your machine. + + You can always use the cloud-hosted solution (e.g. [Neon](https://neon.tech/), [Turso](https://turso.tech/) for database) for your projects. + + +We have a few commands to help you manage the services containers (for local development). + +### Starting containers + +To start the services containers, run: + +```bash +pnpm services:start +``` + +It will run all the services containers. You can check their configs in `docker-compose.yml`. + +### Setting up services + +To setup all the services, run: + +```bash +pnpm services:setup +``` + +It will start all the services containers and run necessary setup steps. + +### Stopping containers + +To stop the services containers, run: + +```bash +pnpm services:stop +``` + +### Displaying status + +To check the status and logs of the services containers, run: + +```bash +pnpm services:status +``` + +### Displaying logs + +To display the logs of the services containers, run: + +```bash +pnpm services:logs +``` + +### Database commands + +We have a few commands to help you manage the database leveraging [Drizzle CLI](https://orm.drizzle.team/kit-docs/commands). + +#### Generating migrations + +To generate a new migration, run: + +```bash +pnpm with-env turbo db:generate +``` + +It will create a new migration `.sql` file in the `packages/db/migrations` folder. + +#### Running migrations + +To run the migrations against the db, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:migrate +``` + +It will apply all the pending migrations. + +#### Pushing changes directly + + + Make sure you know what you're doing before pushing changes directly to the db. + + +To push changes directly to the db, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:push +``` + +It lets you push your schema changes directly to the database and omit managing SQL migration files. + +#### Checking database status + +To check the status of the database, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:status +``` + +It will display the status of the applied migrations and the pending ones. + +```bash +Applied migrations: +- 0000_cooing_vargas +- 0001_curious_wallflower +- 0002_good_vertigo +- 0003_peaceful_devos +- 0004_fat_mad_thinker +- 0005_yummy_bucky +- 0006_glorious_vargas + +Pending migrations: +- 0007_nebulous_havok +``` + +#### Resetting database + +To reset the database, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:reset +``` + +It will reset the database to the initial state. + +#### Seeding database + +To seed the database with some example data (for development purposes), run: + +```bash +pnpm with-env turbo db:seed +``` + +It will populate your database with some example data. + +#### Checking database + +To check the database schema consistency, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:check +``` + +#### Studying database + +To study the database schema in the browser, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:studio +``` + +This will start the Studio on [https://local.drizzle.studio](https://local.drizzle.studio). + +## Tests commands + +### Running tests + +To run the tests, run: + +```bash +pnpm test +``` + +This will run all the tests in the project using Turbo tasks. As it leverages Turbo caching, it's [recommended](/docs/web/tests/unit#configuration) to run it in your CI/CD pipeline. + +### Running tests projects + +To run tests for all Vitest [Test Projects](https://vitest.dev/guide/projects), run: + +```bash +pnpm test:projects +``` + +This will run all the tests in the project using Vitest. + +### Watching tests + +To watch the tests, run: + +```bash +pnpm test:projects:watch +``` + +This will watch the tests for all [Test Projects](https://vitest.dev/guide/projects) and run them automatically when you make changes. + +### Generating code coverage + +To generate code coverage report, run: + +```bash +pnpm turbo test:coverage +``` + +This will generate a code coverage report in the `coverage` directory under `tooling/vitest` package. + +### Viewing code coverage + +To preview the code coverage report in the browser, run: + +```bash +pnpm turbo test:coverage:view +``` + +This will launch the report's `.html` file in your default browser. + + +--- +url: /docs/extension/installation/conventions +title: Conventions +description: Some standard conventions used across TurboStarter codebase. +--- + +You're not required to follow these conventions: they're simply a standard set of practices used in the core kit. If you like them - we encourage you to keep these during your usage of the kit - so to have consistent code style that you and your teammates understand. + +## Turborepo Packages + +In this project, we use [Turborepo packages](https://turbo.build/repo/docs/core-concepts/internal-packages) to define reusable code that can be shared across multiple applications. + +* **Apps** are used to define the main application, including routing, layout, and global styles. +* **Packages** shares reusable code add functionalities across multiple applications. They're configurable from the main application. + + + **Recommendation:** Do not create a package for your app code unless you plan to reuse it across multiple applications or are experienced in writing library code. + + If your application is not intended for reuse, keep all code in the app folder. This approach saves time and reduces complexity, both of which are beneficial for fast shipping. + + **Experienced developers:** If you have the experience, feel free to create packages as needed. + + +## Imports and Paths + +When importing modules from packages or apps, use the following conventions: + +* **From a package:** Use `@turbostarter/package-name` (e.g., `@turbostarter/ui`, `@turbostarter/api`, etc.). +* **From an app:** Use `~/` (e.g., `~/components`, `~/config`, etc.). + +## Enforcing conventions + +* [Prettier](https://prettier.io/) is used to enforce code formatting. +* [ESLint](https://eslint.org/) is used to enforce code quality and best practices. +* [TypeScript](https://www.typescriptlang.org/) is used to enforce type safety. + + + + + + + + + +## Code health + +TurboStarter provides a set of tools to ensure code health and quality in your project. + +### Github Actions + +By default, TurboStarter sets up Github Actions to run tests on every push to the repository. You can find the Github Actions configuration in the `.github/workflows` directory. + +The workflow has multiple stages: + +* `format` - runs Prettier to format the code. +* `lint` - runs ESLint to check for linting errors. +* `typecheck` - runs TypeScript to check for type errors. + +### Git hooks + +Together with TurboStarter we have set up a `commit-msg` hook which will check if your commit message follows the [conventional commit](https://www.conventionalcommits.org/en/v1.0.0/) message format. This is important for generating changelogs and keeping a clean commit history. + +Although we didn't ship any pre-commit hooks (we believe in shipping fast with moving checking code responsibility to CI), you can easily add them by using [Husky](https://typicode.github.io/husky/#/). + +#### Setting up the Pre-Commit Hook + +To do so, create a `pre-commit` file in the `./..husky` directory with the following content: + +```bash +#!/bin/sh + +pnpm typecheck +pnpm lint +``` + +Turborepo will execute the commands for all the affected packages - while skipping the ones that are not affected. + +#### Make the Pre-Commit Hook Executable + +```bash +chmod +x ./.husky/pre-commit +``` + +To test the pre-commit hook, try to commit a file with linting errors or type errors. The commit should fail, and you should see the error messages in the console. + + +--- +url: /docs/extension/installation/dependencies +title: Managing dependencies +description: Learn how to manage dependencies in your project. +--- + +As the package manager we chose [pnpm](https://pnpm.io/). + + + It is a fast, disk space efficient package manager that uses hard links and symlinks to save one version of a module only ever once on a disk. It also has a great [monorepo support](https://pnpm.io/workspaces). Of course, you can change it to use [Bun](https://bunpkg.com), [yarn](https://yarnpkg.com) or [npm](https://www.npmjs.com) with minimal effort. + + +## Install dependency + +To install a package you need to decide whether you want to install it to the root of the monorepo or to a specific workspace. Installing it to the root makes it available to all packages, while installing it to a specific workspace makes it available only to that workspace. + +To install a package globally, run: + +```bash +pnpm add -w +``` + +To install a package to a specific workspace, run: + +```bash +pnpm add --filter +``` + +For example: + +```bash +pnpm add --filter @turbostarter/ui motion +``` + +It will install `motion` to the `@turbostarter/ui` workspace. + +## Remove dependency + +Removing a package is the same as installing but with the `remove` command. + +To remove a package globally, run: + +```bash +pnpm remove -w +``` + +To remove a package from a specific workspace, run: + +```bash +pnpm remove --filter +``` + +## Update a package + +Updating is a bit easier since there is a nice way to update a package in all workspaces at once: + +```bash +pnpm update -r +``` + + + When you update a package, pnpm will respect the [semantic versioning](https://docs.npmjs.com/about-semantic-versioning) rules defined in the `package.json` file. If you want to update a package to the latest version, you can use the `--latest` flag. + + +## Renovate bot + +By default, TurboStarter comes with [Renovate](https://www.npmjs.com/package/renovate) enabled. It is a tool that helps you manage your dependencies by automatically creating pull requests to update your dependencies to the latest versions. You can find its configuration in the `.github/renovate.json` file. Learn more about it in the [official docs](https://docs.renovatebot.com/configuration-options/). + +When it creates a pull request, it is treated as a normal PR, so all tests and preview deployments will run. **It is recommended to always preview and test the changes in the staging environment before merging the PR to the main branch to avoid breaking the application.** + + + + +--- +url: /docs/extension/installation/development +title: Development +description: Get started with the code and develop your browser extension. +--- + +## Prerequisites + +To get started with TurboStarter, ensure you have the following installed and set up: + +* [Node.js](https://nodejs.org/en) (22.x or higher) +* [Docker](https://www.docker.com) (only if you want to use local services e.g. database) +* [pnpm](https://pnpm.io) + +## Project development + + + + ### Install dependencies + + Install the project dependencies by running the following command: + + ```bash + pnpm i + ``` + + + It is a fast, disk space efficient package manager that uses hard links and symlinks to save one version of a module only ever once on a disk. It also has a great [monorepo support](https://pnpm.io/workspaces). Of course, you can change it to use [Bun](https://bunpkg.com), [yarn](https://yarnpkg.com) or [npm](https://www.npmjs.com) with minimal effort. + + + + + ### Setup environment variables + + Create a `.env.local` files from `.env.example` files and fill in the required environment variables. + + You can use the following command to recursively copy the `.env.example` files to the `.env.local` files: + + + + ```bash + find . -name ".env.example" -exec sh -c 'cp "$1" "${1%.example}.local"' _ {} \; + ``` + + + + ```bash + Get-ChildItem -Recurse -Filter ".env.example" | ForEach-Object { + Copy-Item $_.FullName ($\_.FullName -replace '\.example$', '.local') + } + ``` + + + + Check [Environment variables](/docs/extension/configuration/environment-variables) for more details on setting up environment variables. + + + + ### Setup services + + If you want to use local services like database etc. (**recommended for development purposes**), ensure Docker is running, then setup them with: + + ```bash + pnpm services:setup + ``` + + This command initiates the containers and runs necessary setup steps, ensuring your services are up to date and ready to use. + + + + ### Start development server + + To start the application development server, run: + + ```bash + pnpm dev + ``` + + Your development server should now be running 🎉 + + WXT will create a dev bundle for your extension and start a live-reloading development server, which will automatically update your extension bundle and reload your browser on source code changes. + + It also makes the icon grayscale to distinguish between development and production extension bundles. + + + + ### Load the extension + + + + Head over to `chrome://extensions` and enable **Developer Mode**. + + ![Developer mode](/images/docs/extension/chrome/developer-mode.png) + + Click on "Load Unpacked" and navigate to your extension's `apps/extension/build/chrome-mv3` directory. + + To see your popup, click on the puzzle piece icon on the Chrome toolbar, and click on your extension. + + ![Pin to toolbar](/images/docs/extension/chrome/pin.png) + + + Pin your extension to the Chrome toolbar for easy access by clicking the pin button. + + + + + Head over to `about:debugging` and click on "This Firefox". + + Click on "Load Temporary Add-on" and navigate to your extension's `apps/extension/build/firefox-mv2` directory. Pick any file to load the extension. + + ![Load temporary add-on](/images/docs/extension/firefox/load.png) + + The extension now installs, and remains installed until you restart Firefox. + + To see your popup, click on your extension icon on the Firefox toolbar. + + ![Popup](/images/docs/extension/firefox/popup.png) + + + Loaded extension starts as pinned on the Firefox toolbar. Don't remove it to easily access it later. + + + + + + You can also configure your development server to automatically start the browser when you start the server. To do it, create a `web-ext.config.ts` file in a root of your extension and configure it with your browser [binaries](https://wxt.dev/guide/essentials/config/browser-startup.html#set-browser-binaries) and [argumens](https://wxt.dev/guide/essentials/config/browser-startup.html#persist-data). + + Learn more in the [official documentation](https://wxt.dev/guide/essentials/config/browser-startup.html). + + + + + ### Publish to stores + + When you're ready to publish the project to the stores, follow the [guidelines](/docs/extension/marketing) and [checklist](/docs/extension/publishing/checklist) to ensure everything is set up correctly. + + + + +--- +url: /docs/extension/installation/editor-setup +title: Editor setup +description: Learn how to set up your editor for the fastest development experience. +--- + +Of course you can use every IDE you like, but you will have the best possible developer experience with this starter kit when using **VSCode-based** editor with the suggested settings and extensions. + +## Settings + +We have included most recommended settings in the `.vscode/settings.json` file to make your development experience as smooth as possible. It include mostly configs for tools like Prettier, ESLint and Tailwind which are used to enforce some conventions across the codebase. You can adjust them to your needs. + +## LLM rules + +We exposed a special endpoint that will scan all the docs and return the content as a text file which you can use to train your LLM or put in a prompt. You can find it at [/llms.txt](/llms.txt). + +The repository also includes a custom rules for most popular AI editors and agents to ensure that AI completions are working as expected and following our conventions. + +### AGENTS.md + +We've integrated specific rules that help maintain code quality and ensure AI-assisted completions align with our project standards. + +You can find them in the `AGENTS.md` file at the root of the project. This format is a standardized way to instruct AI agents to follow project conventions when generating code and it's used by over **20,000** open-source projects - including [Cursor](https://cursor.sh/), [Aider](https://aider.chat/), [Codex from OpenAI](https://openai.com/blog/openai-codex/), [Jules from Google](https://jules.ai/), [Windsurf](https://windsurf.dev/), and many others. + +```md title="AGENTS.md" +### Code Style and Structure + +- Write concise, technical TypeScript code with accurate examples +- Use functional and declarative programming patterns; avoid classes +- Prefer iteration and modularization over code duplication + +### Naming Conventions + +.... +``` + +To learn more about `AGENTS.md` rules check out the [official documentation](https://agents.md/). + +## Extensions + +Once you cloned the project and opened it in VSCode you should be promted to install suggested extensions which are defined in the `.vscode/extensions.json` automatically. In case you rather want to install them manually you can do so at any time later. + +These are the extensions we recommend: + +### ESLint + +Global extension for static code analysis. It will help you to find and fix problems in your JavaScript code. + + + +### Prettier + +Global extension for code formatting. It will help you to keep your code clean and consistent. + + + +### Pretty TypeScript Errors + +Improves TypeScript error messages shown in the editor. + + + +### Tailwind CSS IntelliSense + +Adds IntelliSense for Tailwind CSS classes to enable autocompletion and linting. + + + + +--- +url: /docs/extension/installation/structure +title: Project structure +description: Learn about the project structure and how to navigate it. +--- + +The main directories in the project are: + +* `apps` - the location of the main apps +* `packages` - the location of the shared code and the API + +### `apps` Directory + +This is where the apps live. It includes web app (Next.js), mobile app (React Native - Expo), and the browser extension (WXT - Vite + React). Each app has its own directory. + +### `packages` Directory + +This is where the shared code and the API for packages live. It includes the following: + +* shared libraries (database, mailers, cms, billing, etc.) +* shared features (auth, mails, billing, ai etc.) +* UI components (buttons, forms, modals, etc.) + +All apps can use and reuse the API exported from the packages directory. This makes it easy to have one, or many apps in the same codebase, sharing the same code. + +## Repository structure + +By default the monorepo contains the following apps and packages: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +## Browser extension application structure + +The browser extension application is located in the `apps/extension` folder. It contains the following folders: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +--- +url: /docs/extension/installation/update +title: Updating codebase +description: Learn how to update your codebase to the latest version. +--- + +If you've been following along with our previous guides, you should already have a Git repository set up for your project, with an `upstream` remote pointing to the original repository. + +Updating your project involves fetching the latest changes from the `upstream` remote and merging them into your project. Let's dive into the steps! + + + + ## Stash changes + + + If you don't have any changes to stash, you can skip this step and proceed with the update process. + + Alternatively, you can [commit](https://git-scm.com/docs/git-commit) your changes. + + + If you have any uncommitted changes, stash them before proceeding. It will allow you to avoid any conflicts that may arise during the update process. + + ```bash + git stash + ``` + + This command will save your changes in a temporary location, allowing you to retrieve them later. Once you're done updating, you can apply the stash to your working directory. + + ```bash + git stash apply + ``` + + + + ## Pull changes + + Pull the latest changes from the `upstream` remote. + + ```bash + git pull upstream main + ``` + + When prompted the first time, please opt for merging instead of rebasing. + + Don't forget to run `pnpm i` in case there are any updates in the dependencies. + + + + ## Resolve conflicts + + If there are any conflicts during the merge, Git will notify you. You can resolve them by opening the conflicting files in your code editor and making the necessary changes. + + + If you find conflicts in the `pnpm-lock.yaml file`, accept either of the two changes (avoid manual edits), then run: + + ```bash + pnpm i + ``` + + Your lock file will now reflect both your changes and the updates from the upstream repository. + + + + + ## Run a health check + + After resolving the conflicts, it's time to test your project to ensure everything is working as expected. Run your project locally and navigate through the various features to verify that everything is functioning correctly. + + For a quick health check, you can run: + + ```bash + pnpm lint + pnpm typecheck + + ``` + + If everything looks good, you're all set! Your project is now up to date with the latest changes from the `upstream` repository. + + + + ## Commit and push + + Once everything is working fine, don't forget to commit your changes using: + + ```bash + git commit -m "" + ``` + + and push them to your remote repository with: + + ```bash + git push origin + ``` + + + + +--- +url: /docs/extension/internationalization +title: Internationalization +description: Learn how to internationalize your extension. +--- + +Turbostarter's extension uses [i18next](https://www.i18next.com/) and web cookies to store the language preference of the user. This allows the extension to be fully internationalized. + + + We use i18next because it's a robust and widely-adopted internationalization framework that works seamlessly with React. + + The combination with web cookies allows us to persistently store language preferences across all extension contexts and share it with the web app while maintaining excellent performance and browser compatibility. + + +![i18next logo](/images/docs/i18next.jpg) + +## Configuration + +The global configuration is defined in the `@turbostarter/i18n` package and shared across all applications. You can read more about it in the [web configuration](/docs/web/internationalization/configuration) documentation. + +By default, the locale is automatically detected based on the user's device settings. You can override it and set the default locale of your mobile app in the [app configuration](/docs/extension/configuration/app) file. + +Also, the locale configuration is **shared between the web app and the extension** (same as [session](/docs/extension/auth/session)), which means that changing the locale in one place will automatically update it in the other. It's a common pattern for modern apps, simplifying the user experience and reducing the maintenance effort. + +### Cookies + +When a user first opens the [web app](/docs/web), the locale is detected and a cookie is set. This cookie is used to remember the user's language preference. + +You can find its value in the *Cookies* tab of the developer tools of your browser: + +![Locale cookie](/images/docs/extension/locale-cookie.png) + +To enable your extension to read the cookie and that way share the locale settings with the web app, you need to set the cookies permission in the `wxt.config.ts` under `manifest.permissions` field: + +```ts +export default defineConfig({ + manifest: { + permissions: ["cookies"], + }, +}); +``` + +And to be able to read the cookie from your app url, you need to set host\_permissions, which will include your app url: + +```ts +export default defineConfig({ + manifest: { + host_permissions: ["http://localhost/*", "https://your-app-url.com/*"], + }, +}); +``` + +Then you would be able to share the cookie between your apps and also read its value using `browser.cookies` API. + + + Avoid using `` in `host_permissions`. It affects all urls and may cause security issues, as well as a [rejection](https://developer.chrome.com/docs/webstore/review-process#review-time-factors) from the destination store. + + + + + + + + +## Translating extension + +To translate individual components and screens, you can use the well-known `useTranslation` hook. + +```tsx +import { useTranslation } from "@turbostarter/i18n"; + +export const Popup = () => { + const { t } = useTranslation(); + + return
{t("hello")}
; +}; +``` + +That's the recommended way to translate stuff inside your extension. + +### Store presence + +As we saw in the [manifest](/docs/extension/configuration/manifest#locales) section, you can also localize your extension's store presence (like title, description, and other metadata). This allows you to customize how your extension appears in different web stores based on the user's language. + +Each store has specific requirements for localization: + +* [Chrome Web Store](https://developer.chrome.com/docs/webstore/cws-dashboard-listing/) requires a `_locales` directory with JSON files for each language +* [Firefox Add-ons](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Internationalization) uses a similar structure but with some differences in the manifest +* [Edge Add-ons](https://learn.microsoft.com/en-us/microsoft-edge/extensions/publish/publish-extension#supporting-multiple-languages) uses the same structure as Chrome Web Store + +Although most of the config is abstracted behind common structure, please follow the store-specific guides below for detailed instructions on setting up localization for your extension's store listing. + + + + + + + + + + + +## Language switcher + +TurboStarter ships with a language customizer component that allows users to switch between languages in your extension. You can import and use the `LocaleCustomizer` component in your popup, options page, or any other extension view: + +```tsx +import { LocaleCustomizer } from "@turbostarter/ui-web/i18n"; + +export const Popup = () => { + return ; +}; +``` + + + As the web app and extension share the same i18n configuration (cookie), changing the language in one will affect the other. **This is intentional** and ensures a consistent experience across both platforms, since your extension likely serves as a companion to the web app and should maintain the same language preferences. + + +## Best practices + +Here are key best practices for managing translations in your browser extension: + +* Use descriptive, hierarchical translation keys + + ```ts + // ✅ Good + "popup.settings.language"; + "content.toolbar.save"; + + // ❌ Bad + "saveButton"; + "text1"; + ``` + +* Organize translations by extension views and features + + ``` + _locales/ + ├── en/ + │ ├── messages.json + │ ├── popup.json + │ └── options.json + └── es/ + ├── messages.json + ├── popup.json + └── options.json + ``` + +* Handle fallback languages gracefully + +* Keep manifest descriptions localized for store listings + +* Consider context in translations: + + ```ts + // Context-aware messages + t("button.save", { context: "document" }); // "Save document" + t("button.save", { context: "settings" }); // "Apply changes" + ``` + +* Use placeholders for dynamic content: + + ```ts + // With variables + t("status.saved", { time: "2 minutes ago" }); // "Last saved 2 minutes ago" + + // With plurals + t("items", { count: 5 }); // "5 items" + ``` + +* Keep translations in sync between extension views + +* Cache translations for offline functionality + + +--- +url: /docs/extension/marketing +title: Marketing +description: Learn how to market your mobile application. +--- + +As you saw in the [Extras](/docs/extension/extras) section, TurboStarter comes with a lot of tips and tricks to make your product better and help you launch your extension faster with higher traffic. + +The same applies to [submission tips](/docs/extension/extras#submission-tips) to help you get your extension approved by the browser stores faster. + +We'll talk more about the whole process of deploying and publishing your extension in the [Publishing](/docs/extension/publishing/checklist) section, here we'll go through some guidelines that you need to follow to make your store's visibility higher. + +## Before you submit + +To help your extension approval go as smoothly as possible, review the common missteps listed below that can slow down the review process or trigger a rejection. This doesn't replace the official guidelines or guarantee approval, but making sure you can check every item on the list is a good start. + +Make sure you: + +* Test your extension thoroughly for crashes and bugs +* Ensure that all extension information and metadata is complete and accurate +* Update your contact information in case the review team needs to reach you +* Provide clear instructions on how to use your extension, including any special setup required +* If your extension requires an account, provide a demo account or a way to test all features without signing up +* Enable and test all backend services to ensure they're live and accessible during review +* Include detailed explanations of non-obvious features in the extension description +* Ensure your extension complies with the specific browser store's policies (e.g., [Chrome Web Store](https://developer.chrome.com/docs/webstore/program-policies/best-practices), [Firefox Add-ons](https://extensionworkshop.com/documentation/publish/add-on-policies/), [Edge Add-ons](https://learn.microsoft.com/en-us/legal/microsoft-edge/extensions/developer-policies) etc.) +* Remove any references to features not supported in browser extensions (e.g., in-app purchases) + +Following these basic steps during development and before submission will help you get your extension approved faster and with fewer issues. + +## Guidelines + +Each store has slightly different guidelines, but some of them are general and can be applied to all stores: + +* **Security**: Your extension must not contain malicious code or behavior that can harm users' devices or data. +* **Performance**: Your extension must be performant and stable, with a smooth user experience. +* **Privacy**: Your extension must respect user privacy and not collect unnecessary data without explicit consent. +* **Compliance**: Your extension must comply with all relevant laws and regulations. + +You can read more about official guidelines for each store in the following links: + +* [Chrome Web Store](https://developer.chrome.com/docs/webstore/program-policies/best-practices) +* [Firefox Add-ons](https://extensionworkshop.com/documentation/publish/add-on-policies/) +* [Edge Add-ons](https://learn.microsoft.com/en-us/microsoft-edge/extensions/developer-guide/best-practices) + +## Common mistakes + +There are a few common mistakes that you should avoid to make sure your extension can be accepted in the stores. The most common ones are: + +* **Not enough description** - make sure to describe all the features of your extension and how it works in your store listing, that way users won't be confused about what your extension does. Also include detailed information in the single purpose field regarding your extension's primary functionality. +* **Privacy issues** - respect user privacy and require as least permissions as possible, don't ask for permissions that are not necessary for your extension to work +* **Customer support** - provide a way to contact you in case the user has any issues with your extension +* **Stay up-to-date** - keep your extension and its documentation up-to-date to ensure a smooth user experience and to prevent issues during the review process. + + + + + + + + + + +--- +url: /docs/extension/monitoring/overview +title: Overview +description: Get started with browser extension monitoring in TurboStarter. +--- + +TurboStarter includes powerful, provider-agnostic monitoring helpers for the browser extension so you can understand **what failed**, **where it failed** (popup, content script, background), and **who it impacted**. The API is intentionally designed for simplicity and extensibility, so you can swap providers without rewriting your extension code. + +## Capturing exceptions + +Extensions have multiple runtimes. To get good coverage, capture errors in the places users actually feel them: + +* **Popup / options UI**: React pages where runtime errors break interactions. +* **Background (service worker)**: long-lived logic like alarms, message routing, and sync. +* **Content scripts**: page integrations where DOM differences and CSP can trigger failures. +* **Manual reporting**: wrap critical flows (auth, billing, webhooks-to-extension sync, imports) with `try/catch` and report with context. + + + + ```tsx + import { captureException } from "@turbostarter/monitoring-extension"; + + export function ExampleButton() { + const onPress = async () => { + try { + /* some risky operation */ + } catch (error) { + captureException(error); + } + }; + + return ; + } + ``` + + + + ```ts + import { captureException } from "@turbostarter/monitoring-extension"; + + browser.runtime.onMessage.addListener((message, _sender, sendResponse) => { + try { + /* handle message */ + sendResponse({ ok: true }); + } catch (error) { + captureException(error); + sendResponse({ ok: false }); + } + }); + ``` + + + + ```ts + import { captureException } from "@turbostarter/monitoring-extension"; + + try { + /* interact with the page DOM */ + } catch (error) { + captureException(error); + } + ``` + + + + + An exception in a content script won't automatically show up in your background logs (and vice versa). Add capture points in each runtime you ship, especially if you do message passing between them. + + +## Identifying users + +Monitoring becomes far more useful once reports can be tied to a stable identity. In extensions you often have two “identities”: + +* **Anonymous, stable install id**: useful before sign-in (and to correlate issues with a device/install). +* **Signed-in user**: once the user authenticates, identify with their user id so issues map to a real account. + +TurboStarter's monitoring layer supports identifying the current user when your auth session resolves. When signed out, pass `null` (or your provider's preferred anonymous identity strategy). + +```tsx title="monitoring.tsx" +import { useEffect } from "react"; +import { identify } from "@turbostarter/monitoring-extension"; +import { authClient } from "~/lib/auth/client"; + +export const MonitoringProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const session = authClient.useSession(); + + useEffect(() => { + if (session.isPending) { + return; + } + + identify(session.data?.user ?? null); + }, [session]); + + return <>{children}; +}; +``` + + + Prefer **stable IDs** over PII. Only attach traits that help debugging (plan, role, extension version) and avoid secrets (tokens, passwords) or sensitive fields unless you've explicitly chosen to send them. + + +## Providers + +The starter supports multiple monitoring providers behind the same API, so you can start with one and switch later. + + + + + + + +## Best practices + + + + Extension issues are often environment-specific. Make sure you can filter by + runtime (popup/background/content script), extension version, and browser. + + + + Focus on crashes and failures that break core flows; skip “expected” states + like validation errors or user cancellations. + + + + Background alarms, retries, and message loops can generate many identical + errors. Guard your capture calls to keep signal high (and costs low). + + + + Don't mix dev/beta/stable releases. Tag builds so you can correlate spikes + with a rollout and verify fixes quickly. + + + +With capture points in each runtime, user identification wired up, and a provider configured, extension monitoring becomes a tight feedback loop: you can spot regressions early, understand which surface area is failing and validate fixes confidently as you ship new versions. + + +--- +url: /docs/extension/monitoring/posthog +title: PostHog +description: Learn how to setup PostHog as your browser extension monitoring provider. +--- + +[PostHog](https://posthog.com/) is a product analytics platform that also supports monitoring capabilities like error tracking and session replay. In extensions, it's especially useful when you want to connect “what broke” with “what the user did” right before the issue occurred. + +TurboStarter keeps monitoring behind a unified API, so you can route exception captures from your popup, background, and content scripts to PostHog without rewriting the call sites. + + + To use PostHog as your monitoring provider, you'll need a PostHog instance. You can use [PostHog Cloud](https://app.posthog.com/signup) or [self-host](https://posthog.com/docs/self-host). + + + + PostHog is also supported as an analytics provider for the extension. If you want to track in-extension events, see the [analytics overview](/docs/extension/analytics/overview) and the [PostHog analytics configuration](/docs/extension/analytics/configuration#posthog). + + +![PostHog banner](/images/docs/web/monitoring/posthog/banner.jpg) + +## Configuration + +Here you'll configure PostHog as the monitoring provider for your extension so exceptions from the popup, background/service worker, and content scripts show up with enough context to debug. + + + + ### Create a project + + Create a PostHog [project](https://app.posthog.com/project/settings) for your extension. You can do this from the [PostHog dashboard](https://app.posthog.com) via the *New Project* action. + + + + ### Activate PostHog as your monitoring provider + + TurboStarter picks the extension monitoring provider through exports in the monitoring package. To route captures to PostHog, export the PostHog implementation from the extension monitoring entrypoint: + + ```ts title="index.ts" + // [!code word:posthog] + export * from "./posthog"; + export * from "./posthog/env"; + ``` + + + + ### Set environment variables + + Add your PostHog project key (and host, if you're not using the default cloud region) to your extension env. Set these locally and in whatever build environment produces your extension bundles: + + ```dotenv title="apps/extension/.env.local" + VITE_POSTHOG_KEY="your-posthog-project-api-key" + VITE_POSTHOG_HOST="https://us.i.posthog.com" + ``` + + + +That's it — load the extension, trigger a test error from the popup/background/content script, and confirm events are arriving in your PostHog project. + +![PostHog error](/images/docs/web/monitoring/posthog/error.png) + +If you want to go beyond basic capture (session replay, feature flags, richer context), follow PostHog's web/extension guidance. + + + + + + + +## Uploading source maps + +**Source maps** map the minified/bundled JavaScript shipped with your extension back to your original source code. Without them, stack traces in PostHog often point at compiled output, which makes debugging much slower. + + + PostHog’s source map flow for web builds relies on injecting metadata into the bundled assets. You must deploy/ship the injected assets, otherwise PostHog can’t match captured errors to the uploaded symbol sets. + + +For extensions built with Vite (which [WXT](https://wxt.dev/) is using under the hood), the high-level flow is: + +* generate `.map` files during the production build +* inject PostHog metadata into the built assets +* upload the injected source maps to PostHog + + + + ### Install the PostHog CLI + + Install the CLI globally: + + ```bash + npm install -g @posthog/cli + ``` + + + + ### Authenticate the CLI + + Authenticate interactively: + + ```bash + posthog-cli login + ``` + + In CI, you can authenticate with environment variables: + + ```dotenv + POSTHOG_CLI_HOST="https://us.posthog.com" + POSTHOG_CLI_ENV_ID="your-posthog-project-id" + POSTHOG_CLI_TOKEN="your-personal-api-key" + ``` + + + + ### Build with source maps enabled + + Make sure your extension build outputs source maps by modifying your `wxt.config.ts` file. + + ```ts title="wxt.config.ts" + import { defineConfig } from "wxt"; + + export default defineConfig({ + /* existing WXT configuration options */ + vite: () => ({ + build: { + sourcemap: "hidden", // [!code ++] Source map generation must be turned on ("hidden", true, etc.) + }, + }), + }); + ``` + + After building, you should have `.js` and `.js.map` files in your output directory. + + + + ### Inject PostHog metadata into the built assets + + Inject release/chunk metadata so PostHog can associate uploaded maps with the shipped bundles: + + ```bash + posthog-cli sourcemap inject --directory ./path/to/assets --project my-extension --version 1.2.3 + ``` + + + + ### Upload source maps + + Upload the injected source maps to PostHog: + + ```bash + posthog-cli sourcemap upload --directory ./path/to/assets + ``` + + + + ### Verify injection and uploads + + After deployment, confirm your production bundles include the injected comment (for example `//# chunkId=...`) and verify symbol sets exist in your PostHog project settings. + + + +With this in place, PostHog can symbolicate extension errors (popup/options UI, background/service worker, and content scripts) so stack traces point back to your original source files. + + + + + + + + +--- +url: /docs/extension/monitoring/sentry +title: Sentry +description: Learn how to setup Sentry as your browser extension monitoring provider. +--- + +[Sentry](https://sentry.io/) is a popular error monitoring and performance tracking platform. It helps you catch and debug issues by collecting exceptions, stack traces, and helpful context from production. + +For browser extensions, that context matters even more: errors can happen in multiple runtimes (popup/options UI, background/service worker, and content scripts). Sentry makes it easier to see what failed and where it happened so you can ship fixes with confidence. + + + To use Sentry, create an account and a project first. You can sign up [here](https://sentry.io/signup). + + +![Sentry banner](/images/docs/web/monitoring/sentry/banner.png) + +## Configuration + +This section walks you through enabling Sentry for your extension and verifying that errors from the popup, background/service worker, and content scripts are captured reliably. + + + + ### Create a project + + Create a Sentry [project](https://docs.sentry.io/product/projects/) for the extension (JavaScript / browser). You can do this from the Sentry [projects dashboard](https://sentry.io/settings/account/projects/) via the *Create Project* flow. + + + + ### Activate Sentry as your monitoring provider + + TurboStarter picks the extension monitoring provider via exports in the monitoring package. To enable Sentry, export the Sentry implementation from the extension monitoring entrypoint: + + ```ts title="index.ts" + // [!code word:sentry] + export * from "./sentry"; + export * from "./sentry/env"; + ``` + + If you need to customize behavior, the provider implementation lives under `packages/monitoring/extension/src/providers/sentry`. + + + + ### Set environment variables + + From your Sentry project settings, add the DSN and environment to your extension env file (and to any [CI/build step](/docs/extension/publishing/checklist#build-your-app) that produces your extension bundles): + + ```dotenv title="apps/extension/.env.local" + VITE_SENTRY_DSN="your-sentry-dsn" + VITE_SENTRY_ENVIRONMENT="your-project-environment" + ``` + + + +That's it — load the extension, trigger a test error from the popup/background/content script, and confirm it shows up in your [Sentry dashboard](https://sentry.io/settings/account/projects/). + +![Sentry error](/images/docs/web/monitoring/sentry/error.jpg) + +For advanced options (sampling, releases, extra context), refer to [Sentry's JavaScript docs](https://docs.sentry.io/platforms/javascript/). + + + + + + + +## Uploading source maps + +**Source maps** map the bundled/minified JavaScript shipped with your extension back to your original source files. Without them, Sentry stack traces often point to compiled output, which makes debugging across popup/background/content-script runtimes much harder. + + + Generating source maps can expose your source code if `.map` files are publicly accessible. Prefer hidden source maps and/or delete them after upload. + + +Sentry can automatically provide readable stack traces for errors using source maps, requiring a [Sentry auth token](https://docs.sentry.io/account/auth-tokens/). + + + + ### Install the Sentry Vite plugin + + Install the package `@sentry/vite-plugin` in `apps/extension/package.json` as a dev dependency. + + ```bash + pnpm i @sentry/vite-plugin -D --filter extension + ``` + + + + ### Add an auth token for uploads + + Create an [auth token in Sentry](https://docs.sentry.io/account/auth-tokens/) and provide it as an environment variable during builds (locally and in your build environment): + + ```dotenv + SENTRY_AUTH_TOKEN="your-sentry-auth-token" + ``` + + + + ### Enable source maps and configure the plugin + + Enable source map generation in your extension build and add `sentryVitePlugin` **after** your other Vite plugins: + + ```ts title="wxt.config.ts" + import { defineConfig } from "wxt"; + import { sentryVitePlugin } from "@sentry/vite-plugin"; + + export default defineConfig({ + /* existing WXT configuration options */ + vite: () => ({ + build: { + sourcemap: "hidden", // [!code ++] Source map generation must be turned on ("hidden", true, etc.) + }, + plugins: [ + sentryVitePlugin({ + org: "your-sentry-org", + project: "your-sentry-project", + authToken: process.env.SENTRY_AUTH_TOKEN, + + sourcemaps: { + // As you're enabling client source maps, you probably want to delete them after they're uploaded to Sentry. + // Set the appropriate glob pattern for your output folder - some glob examples below: + filesToDeleteAfterUpload: [ + "./**/*.map", + ".*/**/public/**/*.map", + "./dist/**/client/**/*.map", + ], + }, + }), + ], + }), + }); + ``` + + + + ### Verify uploads with a production build + + The Sentry Vite plugin doesn't upload in dev/watch mode. Run a production build, then trigger a test error in the extension and confirm stack traces resolve to your original source. + + + +Once this is in place, errors from your extension's compiled bundles (popup/options UI, background/service worker, content scripts) should show **readable stack traces** in Sentry, without shipping source maps to end users. + + + + + + + + +--- +url: /docs/extension/organizations +title: Organizations/teams +description: Learn how to use organizations/teams/multi-tenancy in TurboStarter extension. +--- + +TurboStarter extensions support organizations/teams out of the box by sharing the same authentication session as your web app. The active organization is stored in the session and available to your extension without re-implementing organizations logic. + + + The extension and web app use a single auth session powered by Better Auth. The session includes tenant context (for example, `activeOrganizationId`). When users sign in, switch organizations, or sign out in the web app, the extension picks up these changes automatically. + + Learn more: [Auth → Session](/docs/extension/auth/session). + + +## How it works + +* **No separate auth flow** in the extension. We reuse the web session. +* **Active organization comes from the session** (e.g., `session.activeOrganizationId`). +* **Protected API calls** from the extension include the right cookies, so org‑scoped server logic works as expected. + +![Shared authentication with organizations in extension](/images/docs/extension/organizations.png) + +## Active organization + +Use your existing auth client to read the active organization through the `useActiveOrganization` hook. + +```tsx title="popup.tsx" +import { authClient } from "~/lib/auth"; + +export function Popup() { + const organization = authClient.useActiveOrganization(); + + return <>{organization?.name}; +} +``` + + + If a user switches organizations in the web app, the extension reflects the change through the shared session on the next interaction. For long-lived views, re-read the session or invalidate related queries when appropriate. + + +## Do more with organizations + +Most organization features live in the web app and are exposed via APIs your extension can call. These guides explain the underlying concepts and server behavior your extension builds upon: + + + + + + + + + + + + + + + Looking for the underlying auth setup? Start with [Auth → + Overview](/docs/extension/auth/overview) and [Auth → + Session](/docs/extension/auth/session). + + + +--- +url: /docs/extension/publishing/checklist +title: Checklist +description: Let's publish your TurboStarter extension to stores! +--- + +When you're ready to publish your TurboStarter extension to stores, follow this checklist. + +This process may take a few hours and some trial and error, so buckle up - you're almost there! + + + + ## Create database instance + + **Why it's necessary?** + + A production-ready database instance is essential for storing your application's data securely and reliably in the cloud. [PostgreSQL](https://www.postgresql.org/) is the recommended database for TurboStarter due to its robustness, features, and wide support. + + **How to do it?** + + You have several options for hosting your PostgreSQL database: + + * [Supabase](/docs/extension/recipes/supabase) - Provides a fully managed Postgres database with additional features + * [Vercel Postgres](https://vercel.com/storage/postgres) - Serverless SQL database optimized for Vercel deployments + * [Neon](https://neon.tech/) - Serverless Postgres with automatic scaling + * [Turso](https://turso.tech/) - Edge database built on libSQL with global replication + * [DigitalOcean](https://www.digitalocean.com/products/managed-databases) - Managed database clusters with automated failover + + Choose a provider based on your needs for: + + * Pricing and budget + * Geographic region availability + * Scaling requirements + * Additional features (backups, monitoring, etc.) + + + + ## Migrate database + + **Why it's necessary?** + + Pushing database migrations ensures that your database schema in the remote database instance is configured to match TurboStarter's requirements. This step is crucial for the application to function correctly. + + **How to do it?** + + You basically have two possibilities for doing a migration: + + + + TurboStarter comes with a predefined GitHub Action to handle database migrations. You can find its definition in the `.github/workflows/publish-db.yml` file. + + What you need to do is set your `DATABASE_URL` as a [secret for your GitHub repository](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions). + + Then, you can run the workflow which will publish the database schema to your remote database instance. + + [Check how to run GitHub Actions workflow.](https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/manually-running-a-workflow) + + + + You can also run your migrations locally, although this is not recommended for production. + + To do so, set the `DATABASE_URL` environment variable to your database URL (that comes from your database provider) in the `.env.local` file and run the following command: + + ```bash + pnpm with-env pnpm --filter @turbostarter/db db:migrate + ``` + + This command will run the migrations and apply them to your remote database. + + [Learn more about database migrations.](/docs/web/database/migrations) + + + + + + ## Set up web backend API + + **Why it's necessary?** + + Setting up the backend is necessary to have a place to store your data and to have other features work properly (e.g. authentication, billing or storage). + + **How to do it?** + + Please refer to the [web deployment checklist](/docs/web/deployment/checklist) on how to set up and deploy the web app backend to production. + + + + ## Environment variables + + **Why it's necessary?** + + Setting the correct environment variables is essential for the extension to function correctly. These variables include API keys, database URLs, and other configuration details required for your extension to connect to various services. + + **How to do it?** + + Use our `.env.example` files to get the correct environment variables for your project. Then add them to your CI/CD provider (e.g. [GitHub Actions](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions)) as a [secret](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions). + + + + ## Build your app + + **Why it's necessary?** + + Building your extension is necessary to create a standalone extension bundle that can be published to the stores. + + **How to do it?** + + You basically have two possibilities to build a bundle for your extension: + + + + TurboStarter comes with a predefined GitHub Action to handle building your extension for submission. You can find its definition in the `.github/workflows/publish-extension.yml` file. + + [Check how to run GitHub Actions workflow.](https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/manually-running-a-workflow) + + This will also save the `.zip` file as an [artifact](https://docs.github.com/en/actions/guides/storing-workflow-data-as-artifacts) of the workflow run, so you can download it from there and submit your extension to stores (if configured). + + + + You can also run your build locally, although this is not recommended for production. + + To do it, run the following command: + + ```bash + pnpm turbo build --filter=extension + ``` + + This will build the extension and package it into a `.zip` file. You can find the output in the `build` folder. + + + + + + ## Submit to stores + + **Why it's necessary?** + + Publishing your extension to the stores is required to make it discoverable and accessible to your users. This is the official distribution channel where users can find, install, and trust your extension. + + **How to do it?** + + We've prepared dedicated guides for each store that TurboStarter supports out-of-the-box, please refer to the following pages: + + + + + + + + + + + +That's it! Your extension is now live and accessible to your users, good job! 🎉 + + + * Optimize your store listing description, keywords, and other relevant information for the stores. + * Remove the placeholder content in the extension or replace it with your own. + * Update the favicon, scheme, store images, and logo with your own branding. + + + +--- +url: /docs/extension/publishing/chrome +title: Chrome Web Store +description: Publish your extension to Google Chrome Web Store. +--- + +[Chrome Web Store](https://chromewebstore.google.com/) is the most popular store for browser extensions, as it makes them available in any Chromium-based browser, including Google Chrome, Edge, Brave, and many others. + +To submit your extension to Chrome Web Store, you'll need to complete a few steps. Here, we'll go through them. + + + Make sure your extension follows the [guidelines](/docs/extension/marketing) and other requirements to increase your chances of getting approved. + + +## Developer account + +Before you can publish items on the Chrome Web Store, you must register as a CWS developer and pay a one-time registration fee. You must provide a developer email when you create your developer account. + +To register, just access the [developer console](https://chrome.google.com/webstore/devconsole). The first time you do this, the following registration screen will appear. First, agree to the developer agreement and policies, then pay the registration fee. + +![Chrome registration fee](/images/docs/extension/chrome/fee.png) + +Once you pay the registration fee and agree to the terms, your account will be created, and you'll be able to proceed to fill out additional information about it. + +![Chrome developer account](/images/docs/extension/chrome/account.png) + +There are a few fields that you'll need to fill in: + +* **Publisher name**: Appears under the title of each of your extensions. If you are a verified publisher, you can display an official publisher URL instead. +* **Verified email**: Verifying your contact email address is required when you set up a new developer account. It's only displayed under your extensions' contact information. Any notifications will be sent to your Chrome Web Store developer account email. +* **Physical address**: Only items that offer functionality to purchase items, additional features, or subscriptions must include a physical address. + + + +## Submission + +After registering your developer account, setting it up, and preparing your extension, you're ready to publish it to the store. + +You can submit your extension in two ways: + +* **Manually**: By uploading your extension's bundle directly to the store. +* **Automatically**: By using GitHub Actions to submit your extension to the stores. + +**The first submission must be done manually, while subsequent updates can be submitted automatically.** We'll go through both approaches. + +### Manual submission + +To manually submit your extension to stores, you will first need to get your extension bundle. If you ran the build step locally, you should already have the `.zip` file in your extension's `build` folder. + +If you used GitHub Actions to build your extension, you can find the results in the workflow run. Download the artifacts and save them on your local machine. + +Then, use the following steps to upload your item: + +1. Go to the [Chrome Web Store Developer Dashboard](https://chrome.google.com/webstore/devconsole/). +2. Sign in to your developer account. +3. Click on the *Add new item* button. +4. Click *Choose file* > *your zip file* > *Upload*. If your item's manifest and other contents are valid, you will see a new item in the dashboard. + +![Chrome extension page](/images/docs/extension/chrome/extension-page.png) + +After you upload the bundle, you'll need to fill in the extension's details, such as the icons, privacy settings, permissions justification, and other information. + +Please refer to the official guides on how to set up your extension's details. + + + + + + + + + +### Automated submission + + + The first submission of your extension to Chrome Web Store must be done manually because you need to provide the store's credentials and extension ID to automation, which will be available only after the first bundle upload. + + +TurboStarter comes with a pre-configured GitHub Actions workflow to submit your extension to web stores automatically. It's located in the `.github/workflows/publish-extension.yml` file. + +What you need to do is fill the environment variables with your store's credentials and extension's details and set them as a [secrets in your Github repository](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions) under correct names: + +```yaml title="publish-extension.yml" +env: + CHROME_EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }} + CHROME_CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }} + CHROME_CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }} + CHROME_REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }} +``` + +Please refer to the [official guide](https://github.com/PlasmoHQ/bms/blob/main/tokens.md#chrome-web-store-api) to learn how to get these credentials correctly. + +That's it! You can [run the workflow](https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/manually-running-a-workflow) and it will submit your extension to the Chrome Web Store 🎉 + + + This workflow will also try to send your extension to review, but it's not guaranteed to happen. You need to have all required information filled in your extension's details page to make it possible. + + Even then, when you introduce some **breaking change** (e.g. add another permission), you'll need to update your extension store metadata and automatic submit won't be possible. + + To opt out of this behavior (and use only automatic uploading to store, but not sending to review) you can set `--chrome-skip-submit-review` flag in the `publish-extension.yml` file for the `wxt submit` command: + + ```yaml title="publish-extension.yml" + // [!code word:--chrome-skip-submit-review] + - name: 💨 Publish! + run: | + npx wxt submit \ + --chrome-zip apps/extension/build/*-chrome.zip --chrome-skip-submit-review + ``` + + Then, your extension bundle will be uploaded to the store, but you will need to send it to review manually. + + Check out the [official documentation](https://wxt.dev/api/cli/wxt-submit) for more customization options. + + + + + + + + +## Review + +After filling out the information about your item, you are ready to send it to review. Click on *Submit for review* button and confirm that you want to submit your item in the following dialog: + +![Chrome submit for review](/images/docs/extension/chrome/send-to-review.png) + +The confirmation dialog shown above also lets you control the timing of your item's publishing. If you uncheck the checkbox, your item will **not** be published immediately after its review is complete. Instead, you'll be able to manually publish it at a time of your choosing once the review is complete. + +After you submit the item for review, it will undergo a review process. The time for this review depends on the nature of your item. See [Understanding the review process](https://developer.chrome.com/docs/webstore/review-process) for more details. + +There are important emails like take down or rejection notifications that are enabled by default. To receive an email notification when your item is published or staged, you can enable notifications on the *Account page*. + +![Chrome notifications](/images/docs/extension/chrome/notifications.png) + +The review status of your item appears in the [developer dashboard](https://chrome.google.com/webstore/devconsole) next to each item. The status can be one of the following: + +* **Published**: Your item is available to all users. +* **Pending**: Your item is under review. +* **Rejected**: Your item was rejected by the store. +* **Taken Down**: Your item was taken down by the store. + +![Chrome extension status](/images/docs/extension/chrome/review-status.png) + +You'll receive an email notification when the status of your item changes. + + + If your extension has been determined to violate one or more terms or policies, you will receive an email notification that contains the violation description and instructions on how to rectify it. + + If you did not receive an email within a week, check the status of your item. If your item has been rejected, you can see the details on the *Status* tab of your item. + + ![Chrome extension rejected](/images/docs/extension/chrome/rejection.png) + + You'll need to fix the issues and upload a new version of your extension, make sure to follow the [guidelines](/docs/extension/marketing) or check [publishing troubleshooting](/docs/extension/troubleshooting/publishing) for more info. + + If you have been informed about a violation and you do not rectify it, your item will be taken down. See [Violation enforcement](https://developer.chrome.com/docs/webstore/review-process#enforcement) for more details. + + +You can learn more about the review process in the official guides listed below. + + + + + + + + +--- +url: /docs/extension/publishing/edge +title: Edge Add-ons +description: Publish your extension to Microsoft Edge Add-ons. +--- + +[Microsoft Edge Add-ons](https://microsoftedge.microsoft.com/addons/) distributes extensions to Microsoft Edge users. If you already have a Chromium-based extension, you can submit it to Edge with minimal changes. + +This guide walks you through manual submission and optional automation, aligned with the official process. + + + Make sure your extension follows the general [guidelines](/docs/extension/marketing) and the Edge Add-ons developer policies to increase your chances of approval. + + +## Developer account + +To enroll in the Microsoft Edge program you need to have a Microsoft account. If you don't have one, you can create one [here](https://account.microsoft.com/account/signup?signin=1\&ru=https://account.microsoft.com/account/login?loginMethod=email). + +![Microsoft account](/images/docs/extension/edge/create-microsoft-account.png) + +Next, before you can publish your extension to Edge Add-ons, you need to register your developer account in [Partner Center](https://partner.microsoft.com/dashboard/microsoftedge/public/login?ref=dd). Fill out the required fields and submit the form with *Finish* button. Wait for the email that your account has been verified - you're ready to submit your extension! + +![Partner Center](/images/docs/extension/edge/developer-account.png) + + + +## Submission + +After your account is ready and the extension bundle is prepared, you can publish it. There are two paths: + +* **Manually**: Upload your `.zip` package through Partner Center. +* **Automatically**: Use CI to upload new versions after the first manual submission. + +**The first submission should be done manually.** Subsequent updates can be automated once you have your extension ID and required credentials. + +### Manual submission + +To manually submit your extension to stores, you will first need to get your extension bundle. If you ran the build step locally, you should already have the .zip file in your extension's build folder. + +If you used GitHub Actions to build your extension, you can find the results in the workflow run. Download the artifacts and save them on your local machine. + +Then, use the following steps to upload your item: + + + + #### Sign in to your developer account + + Go to the [Partner Center](https://partner.microsoft.com/dashboard/microsoftedge/public/login?ref=dd) and sign in to your developer account. + + + + #### Create new extension + + Click the *Create new extension* button to start a new submission. + + ![Create new extension](/images/docs/extension/edge/create.png) + + + + #### Upload the extension package + + The *Extension overview* page shows information for a specific extension: + + ![Extension overview](/images/docs/extension/edge/upload.png) + + To upload your extension package: + + 1. Click *Packages* in the left sidebar. + 2. Drag and drop your `.zip` file or click *Browse your files* to select it. + 3. Wait for validation to complete. If it fails, fix any issues and re-upload. + 4. Review the extracted extension details and click *Continue*. + + + + #### Set availability + + Choose visibility: + + * `Public`: discoverable in the store and via search. + * `Hidden`: not discoverable; accessible via direct listing URL only. + + Select markets where the extension is available. You can later add or remove markets; existing users retain access to installed versions. + + ![Availability](/images/docs/extension/edge/availability.png) + + + + #### Enter properties + + Provide category, privacy policy requirements, privacy policy URL (if applicable), website URL, and support contact. + + These are shown to users on the listing and must meet policy requirements. + + ![Properties](/images/docs/extension/edge/properties.png) + + Follow the [official documentation](https://learn.microsoft.com/en-us/microsoft-edge/extensions/publish/publish-extension#step-4-enter-properties-describing-your-extension) for more details. + + + + #### Add store listing details + + Fill in the store listing details for your extension: + + * **Display name**: The name shown in the store (from your manifest file). + * **Description**: A detailed description (250-5000 characters) explaining what your extension does and why users should install it. + * **Extension Store logo**: A 300x300 pixel logo representing your extension. + * **Screenshots**: Up to 10 screenshots (640x480 or 1280x800 pixels) showing your extension's functionality. + * **Small/Large promotional tiles**: Optional promotional images for store featuring. + * **YouTube video URL**: Optional promotional video. + * **Search terms**: Keywords to help users discover your extension (up to 21 words total). + + You must provide the description and logo for each supported language. Other fields are optional but recommended for better discoverability. + + ![Store listing details](/images/docs/extension/edge/store-listing.png) + + Follow the [official documentation](https://learn.microsoft.com/en-us/microsoft-edge/extensions/publish/publish-extension#step-5-add-store-listing-details-for-your-extension) for detailed requirements and best practices. + + + + #### Submit for review + + Complete the submission by providing testing notes to help certification testers understand your extension. + + Click the *Submit* button to open the submission page: + + ![Submit extension](/images/docs/extension/edge/submit.png) + + In the **Notes for certification** text box, provide additional information to help testers properly evaluate your extension. Include any relevant details such as: + + * Test account usernames and passwords + * Steps to access hidden or locked features + * Expected differences based on region or user settings + * Information about changes if this is an update + * Any other context testers need to understand your submission + + Once you've added your notes, click the *Publish* button to submit your extension for certification. + + Your extension will proceed to the certification step, which can take up to seven business days. + + After passing certification, your extension will be published to [Microsoft Edge Add-ons](https://microsoftedge.microsoft.com/addons/) and the status in Partner Center will change to "In the Store". + + + + + + + + + +## Automated submission + + + The first submission of your extension to Microsoft Edge Add-ons must be done manually because you need to provide the store's credentials and extension ID to automation, which will be available only after the first bundle upload. + + +TurboStarter comes with a pre-configured GitHub Actions workflow to submit your extension to web stores automatically. It's located in the .github/workflows/publish-extension.yml file. + +What you need to do is fill the environment variables with your store's credentials and extension's details and set them as a [secrets in your Github repository](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions) under correct names: + +```yaml title="publish-extension.yml" +env: + EDGE_PRODUCT_ID: ${{ secrets.EDGE_PRODUCT_ID }} + EDGE_CLIENT_ID: ${{ secrets.EDGE_CLIENT_ID }} + EDGE_API_KEY: ${{ secrets.EDGE_API_KEY }} +``` + +Please refer to the [official guide](https://github.com/PlasmoHQ/bms/blob/main/tokens.md#edge-add-ons-api-v11) to learn how to get these credentials correctly. + +Once configured, you can manually [trigger the workflow](https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/manually-running-a-workflow) to upload the new version to Edge Add-ons 🎉 + + + This workflow will also try to send your extension to review, but it's not guaranteed to happen. You need to have all required information filled in your extension's details page to make it possible. + + Even then, when you introduce some **breaking change** (e.g. add another permission), you'll need to update your extension store metadata and automatic submit won't be possible. + + To opt out of this behavior (and use only automatic uploading to store, but not sending to review) you can set `--edge-skip-submit-review` flag in the `publish-extension.yml` file for the `wxt submit` command: + + ```yaml title="publish-extension.yml" + // [!code word:--edge-skip-submit-review] + - name: 💨 Publish! + run: | + npx wxt submit \ + --edge-zip apps/extension/build/*-chrome.zip --edge-skip-submit-review + ``` + + Then, your extension bundle will be uploaded to the store, but you will need to send it to review manually. + + Check out the [official documentation](https://wxt.dev/api/cli/wxt-submit) for more customization options. + + + + + + + + +## Review + +After you submit your extension, it enters Microsoft's certification and publishing pipeline. + +1. Preprocessing + * Uploaded packages are queued and scanned. If errors are detected during preprocessing, you'll see a message and must resolve issues before re-uploading. +2. Certification + * Security tests: packages are checked for viruses and malware. + * Content compliance: human review of your listing and content for policy adherence. +3. Release and publishing + * If you selected publish immediately, publishing begins right away; otherwise schedule/hold options apply. + * While publishing, the submission status page shows rollout details. When complete, the status changes from "Publishing" to "In the Store". +4. Edge Add-ons curation and ranking + * Discovery is influenced by quality, relevancy (name, description, popularity, user experience), and popularity (ratings and averages). Security and policy compliance are verified per the developer policies. + +Microsoft may also perform spot checks after publishing to ensure ongoing compliance. + +The review status of your item appears in the [Partner Center](https://partner.microsoft.com/dashboard/microsoftedge/public/login?ref=dd) under the *Overview* page of your item. + +![Edge extension review status](/images/docs/extension/edge/review-status.png) + +You'll receive an email notification when the status of your item changes. + + + If your extension has been determined to violate one or more terms or policies, you will receive an email notification that contains the violation description and instructions on how to rectify it. + + ![Rejection email](/images/docs/extension/edge/rejection-email.png) + + You can also check the reason behind the rejection on the *Certification report* page of your item. + + ![Certification report](/images/docs/extension/edge/certification-report.png) + + You'll need to fix the issues and upload a new version of your extension. Make sure to follow the [guidelines](/docs/extension/marketing) or check [publishing troubleshooting](/docs/extension/troubleshooting/publishing) for more info. + + +You can learn more about the review process in the official guides listed below. + + + + + + + + + + +--- +url: /docs/extension/publishing/firefox +title: Firefox Add-ons +description: Publish your extension to Mozilla Firefox Add-ons. +--- + +Mozilla Firefox doesn't share extensions with [Google Chrome](/docs/extension/publishing/chrome), so you'll need to publish your extension to it separately. + +Here, we'll go through the process of publishing an extension to [Firefox Add-ons](https://addons.mozilla.org/). + + + Make sure your extension follows the [guidelines](/docs/extension/marketing) and other requirements to increase your chances of getting approved. + + +## Developer account + +Before you can publish items on Firefox Add-ons, you must register a developer account. In comparison to the Chrome Web Store, Firefox Add-ons doesn't require a registration fee. + +To register, go to [addons.mozilla.org](https://addons.mozilla.org/) and click on the *Register* button. + +![Mozilla registration](/images/docs/extension/firefox/portal.png) + +It's important to set at least a display name on your profile to increase transparency with users, add-on reviewers, and the greater community. + +You can do it in the *Edit My Profile* section: + +![Mozilla profile](/images/docs/extension/firefox/profile.png) + + + +## Submission + +After registering your developer account, setting it up, and preparing your extension, you're ready to publish it to the store. + +You can submit your extension in two ways: + +* **Manually**: By uploading your extension's bundle directly to the store. +* **Automatically**: By using GitHub Actions to submit your extension to the stores. + +**The first submission must be done manually, while subsequent updates can be submitted automatically.** We'll go through both approaches. + +### Manual submission + +To manually submit your extension to stores, you will first need to get your extension bundle. If you ran the build step locally, you should already have the `.zip` file in your extension's `build` folder. + +If you used GitHub Actions to build your extension, you can find the results in the workflow run. Download the artifacts and save them on your local machine. + +Then, use the following steps to upload your item: + + + + #### Sign in to your developer account + + Go to the [Add-ons Developer Hub](https://addons.mozilla.org/developers/) and sign in to your developer account. + + + + #### Choose distribution method + + You should reach the following page: + + ![Mozilla distribution](/images/docs/extension/firefox/distribution.png) + + Here, you have two ways of distributing your extension: + + * **On this site**, if you want your add-on listed on AMO (Add-ons Manager). + * **On your own**, if you plan to distribute the add-on yourself and don't want it listed on AMO. + + We recommend going with the first option, as it will allow you to reach more users and get more feedback. If you decide to go with the second option, please refer to the [official documentation](https://extensionworkshop.com/documentation/publish/self-distribution/) for more details. + + + + #### Submit your extension + + On the next page, click on *Select file* and choose your extension's `.zip` bundle. + + ![Mozilla upload](/images/docs/extension/firefox/upload.png) + + Once you upload the bundle, the validator checks the add-on for issues and the page updates: + + ![Mozilla validation](/images/docs/extension/firefox/validation.png) + + If your add-on passes all the checks, you can proceed to the next step. + + + You may receive a message that you only have warnings. It's advisable to address these warnings, particularly those flagged as security or privacy issues, as they may result in your add-on failing review. However, **you can continue with the submission**. + + + If the validation fails, you'll need to address the issues and upload a new version of your add-on. + + + + #### Submit source code (if needed) + + You'll need to indicate whether you need to provide the source code of your extension: + + ![Mozilla source code](/images/docs/extension/firefox/source-code.png) + + If you select *Yes*, a section displays describing what you need to submit. Click *Browse* and locate and upload your source code package. See [Source code submission](https://extensionworkshop.com/documentation/publish/source-code-submission/) for more information. + + + You may receive a message that you only have warnings. It's advisable to address these warnings, particularly those flagged as security or privacy issues, as they may result in your add-on failing review. However, **you can continue with the submission**. + + + If the validation fails, you'll need to address the issues and upload a new version of your add-on. + + + + #### Add metadata + + On the next page, you'll need to provide the following additional information about your extension: + + ![Mozilla additional information](/images/docs/extension/firefox/additional-info.png) + + * **Name**: Your add-on's name. + * **Add-on URL**: The URL for your add-on on AMO. A URL is automatically assigned based on your add-on's name. To change this, click Edit. The URL must be unique. You will be warned if another add-on is using your chosen URL, and you must enter a different one. + * **Summary**: A useful and descriptive short summary of your add-on. + * **Description**: A longer description that provides users with details of the extension's features and functionality. + * **This add-on is experimental**: Indicate if your add-on is experimental or otherwise not ready for general use. The add-on will be listed but with reduced visibility. You can remove this flag when your add-on is ready for general use. + * **This add-on requires payment, non-free services or software, or additional hardware**: Indicate if your add-on requires users to make an additional purchase for it to work fully. + * **Select up to 2 Firefox categories for this add-on**: Select categories that describe your add-on. + * **Select up to 2 Firefox for Android categories for this add-on**: Select categories that describe your add-on. + * **Support email and Support website**: Provide an email address and website where users can get in touch when they have questions, issues, or compliments. + * **License**: Select the appropriate license for your add-on. Click Details to learn more about each license. + * **This add-on has a privacy policy**: If any data is being transmitted from the user's device, a privacy policy explaining what is being sent and how it's used is required. Check this box and provide the privacy policy. + * **Notes for Reviewers**: Provide information to assist the AMO reviewer, such as login details for a dummy account, source code information, or similar. + + + + #### Finalize the process + + Once you're ready, click on the *Submit Version* button. + + ![Mozilla submit](/images/docs/extension/firefox/submit.png) + + You can still edit your add-on's details from the dedicated page after submission. + + + + + + + + + +### Automated submission + + + The first submission of your extension to Firefox Add-ons must be done manually because you need to provide the store's credentials and extension ID to automation, which will be available only after the first bundle upload. + + +TurboStarter comes with a pre-configured GitHub Actions workflow to submit your extension to web stores automatically. It's located in the `.github/workflows/publish-extension.yml` file. + +What you need to do is fill the environment variables with your store's credentials and extension's details and set them as a [secrets in your Github repository](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions) under correct names: + +```yaml title="publish-extension.yml" +env: + FIREFOX_EXTENSION_ID: ${{ secrets.FIREFOX_EXTENSION_ID }} + FIREFOX_JWT_ISSUER: ${{ secrets.FIREFOX_JWT_ISSUER }} + FIREFOX_JWT_SECRET: ${{ secrets.FIREFOX_JWT_SECRET }} +``` + +Please refer to the [official guide](https://github.com/PlasmoHQ/bms/blob/main/tokens.md#firefox-add-ons-api) to learn how to get these credentials correctly. + +That's it! You can [run the workflow](https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/manually-running-a-workflow) and it will submit your extension to the Firefox Add-ons 🎉 + + + This workflow will also try to send your extension to review, but it's not guaranteed to happen. You need to have all required information filled in your extension's details page to make it possible. + + Even then, when you introduce some **breaking change** (e.g., add another permission), you'll need to update your extension store metadata and automatic submission won't be possible. + + + + + + + + +## Review + +Once you submit your extension bundle, it's automatically sent to review and will undergo a review process. The time for this review depends on the nature of your item. + +The add-on review process includes the following phases: + +1. **Automatic Review**: Upon upload, the add-on undergoes several automatic validation steps to ensure its general safety. +2. **Content Review**: Shortly after submission, a human reviewer inspects the add-on to ensure that the listing adheres to content review guidelines, including metadata such as the add-on name and description. +3. **Technical Code Review**: The add-on's source code is examined to ensure compliance with review policies. +4. **Basic Functionality Testing**: After the source code is verified as safe, the add-on undergoes basic functionality testing to confirm it operates as described. + +There are important emails like takedown or rejection notifications that are enabled by default. To receive an email notification when your item is published or staged, you can enable notifications in the *Account Settings*. + +![Mozilla notifications](/images/docs/extension/firefox/notifications.png) + +The review status of your item appears in the [developer hub](https://addons.mozilla.org/en-US/firefox/) next to each item. + +![Mozilla review status](/images/docs/extension/firefox/review-status.png) + +You'll receive an email notification when the status of your item changes. + + + If your extension has been determined to violate one or more terms or policies, you will receive an email notification that contains the violation description and instructions on how to rectify it. + + You can also check the reason behind the rejection on the *Status* page of your item. + + ![Mozilla extension rejected](/images/docs/extension/firefox/rejection.png) + + You'll need to fix the issues and upload a new version of your extension. Make sure to follow the [guidelines](/docs/extension/marketing) or check [publishing troubleshooting](/docs/extension/troubleshooting/publishing) for more info. + + +You can learn more about the review process in the official guides listed below. + + + + + + + + +--- +url: /docs/extension/publishing/updates +title: Updates +description: Learn how to update your published extension. +--- + +After publishing your extension to the stores, you can release updates to deliver new features and bug fixes to your users. + +TurboStarter provides a ready-to-use process for updating your extensions. Let's quickly review how it works. + +## Uploading a new version + +The recommended way to update your extension is to submit a new version to the stores. This method is the most reliable, although it may take some time for the new version to be approved and become available to users. + +To submit a new version, simply update the version number in your `package.json` file: + +```json title="package.json" +{ + ... + "version": "1.0.0", // [!code --] + "version": "1.0.1", // [!code ++] + ... +} +``` + +Next, follow the exact same steps as [when you initially published your extension](/docs/extension/publishing/checklist). When submitting your extension for review, be sure to provide release notes describing the new version. + + +--- +url: /docs/extension/recipes/supabase +title: Supabase +description: Learn how to set up Supabase as the database (and optional storage) provider for your TurboStarter project. +--- + +[Supabase](https://supabase.com) is an open-source backend platform built on top of PostgreSQL that provides a managed database, storage, and other features out of the box. + +You can adopt Supabase incrementally - start with just the pieces you need (for example, database only, or database + storage) and add more features over time. There's no requirement to integrate everything at once. + +In this guide, we'll walk you through the process of setting up Supabase as a provider for your TurboStarter project. This could include using it as a [database](https://supabase.com/docs/guides/database), [storage](https://supabase.com/docs/guides/storage), [edge runtime for your API](https://supabase.com/docs/guides/functions) and more. + +## Prerequisites + +Before you start, make sure you have: + +* **TurboStarter project** cloned locally with dependencies installed (you can use our [CLI](/docs/web/cli) to create a new project in seconds) +* **Supabase account** - you can create one at [supabase.com](https://supabase.com/sign-up) +* Basic familiarity with the core database docs: + * [Database overview](/docs/web/database/overview) + * [Migrations](/docs/web/database/migrations) + * [Database client](/docs/web/database/client) + + + + ## Create a new Supabase project + + 1. Go to the [Supabase dashboard](https://supabase.com). + 2. Create a **new project** (choose a strong database password and a region close to your users). + 3. Supabase will automatically provision a **PostgreSQL database** for you. + + ![Create a new Supabase project](/images/docs/web/recipes/supabase/create-project.png) + + Optionally, you can customize the **Security options** by choosing the **Only Connection String** option - it will opt out of autogenerating API for tables inside your database. It's not needed for TurboStarter setup, but of course you can still leverage it for your custom use-cases. + + ![Security options](/images/docs/web/recipes/supabase/security-options.png) + + Once the project is ready, you can fetch the connection string. + + + + ## Get the database connection string + + In the Supabase dashboard: + + 1. Open your project. + 2. Click on the **Connect** button at the top. + 3. Locate the **connection string** for your chosen ORM (it will be under the **ORMs** tab). + + ![Connect application](/images/docs/web/recipes/supabase/connect-app.png) + + Copy this value - you'll use it as your `DATABASE_URL`. + + + In your Supabase connection string, you can see a placeholder like `[YOUR-PASSWORD]`. Make sure to replace this with the actual password you set when creating your Supabase project. + + + + + ## Configure environment variables + + TurboStarter reads database connection settings from the **root** `.env.local` file and uses them inside the `@turbostarter/db` package. + + Create (or update) the `.env.local` file in the **monorepo root**: + + ```dotenv title=".env.local" + DATABASE_URL="postgres://postgres.[YOUR-PROJECT-REF]:[YOUR-PASSWORD]@aws-0-[aws-region].pooler.supabase.com:6543/postgres?pgbouncer=true&connection_limit=1" + ``` + + Replace: + + * `YOUR-PROJECT-REF` with your Supabase project ref + * `YOUR-PASSWORD` with the database password you set when creating the project + * `aws-region` with the region shown in the Supabase connection string + + + These variables are validated in the `@turbostarter/db` package and used to create Drizzle client for your database. + + + For more background on how `DATABASE_URL` is used, see [Database overview](/docs/web/database/overview). + + + + ## Setup your Supabase database + + With `DATABASE_URL` now pointing to Supabase, you can apply the existing TurboStarter schema to your Supabase database. + + From the monorepo root, run: + + ```bash + pnpm with-env pnpm --filter @turbostarter/db db:migrate + ``` + + This will: + + * Use your Supabase `DATABASE_URL` from `.env.local` + * Run all pending SQL migrations from `packages/db/migrations` + * Create the full TurboStarter schema (users, billing, demo tables, etc.) in Supabase + + If you're actively iterating on the schema, you can generate new migrations and apply them as described in [Migrations](/docs/web/database/migrations). + + + After running your migrations, you may want to seed your database with initial data (such as demo users or organizations). You can do this by running the following command: + + ```bash + pnpm with-env pnpm turbo db:seed + ``` + + This will populate your Supabase database with some example data you can use to test your application. + + + + + ## Use Supabase Storage as S3-compatible storage + + TurboStarter's storage layer is designed to work seamlessly with **any S3-compatible provider**. In this section, we'll show how to use [Supabase Storage](/docs/web/storage/overview) as your application's file storage back-end. + + Supabase Storage provides a simple, S3-compatible API and is a great choice if you're already using Supabase for your database. + + ### Create a storage bucket + + 1. In the Supabase dashboard, go to **Storage → Buckets**. + 2. Click **Create bucket** (name it whatever you want, for example `avatars` or `uploads`). + 3. Adjust settings based on your needs (e.g. limit the maximum file size, specify the allowed file types, etc.) + + ![Create a new bucket](/images/docs/web/recipes/supabase/create-bucket.png) + + You can create multiple buckets (for documents, images, videos, etc.) if needed. + + ### Generate S3 access keys in Supabase dashboard + + 1. Go to **Storage → S3 → Access keys**. + 2. Click **New access key**. + 3. Give it a descriptive name and create the key. + 4. Copy the **Access key ID** and **Secret access key** to use in your application. + + ![Generate S3 access keys](/images/docs/web/recipes/supabase/s3-keys.png) + + ### Configure S3 environment variables for Supabase Storage + + In your weba application's `.env.local`, add (or update) the S3 configuration used by TurboStarter's storage layer: + + ```dotenv title=".env.local" + S3_REGION="us-east-1" + S3_BUCKET="avatars" + S3_ENDPOINT="https://[YOUR-PROJECT-REF].supabase.co/storage/v1/s3" + S3_ACCESS_KEY_ID="your-access-key-id" + S3_SECRET_ACCESS_KEY="your-secret-access-key" + ``` + + These variables integrate directly with the storage configuration described in: + + * [Storage overview](/docs/web/storage/overview) + * [Storage configuration](/docs/web/storage/configuration) + + Once set, existing TurboStarter file upload flows (e.g. user avatars, organization logos) will use Supabase Storage via presigned URLs. + + + + ## Run your API on Supabase Edge Functions + + As we're using a [Hono](https://hono.dev) as our API server, you can deploy it as a Supabase Edge Function so it runs close to your users. + + At a high level: + + 1. Install the [Supabase CLI](https://supabase.com/docs/guides/cli) and initialize a Supabase project locally with `supabase init`. + 2. Create a new [Edge Function](https://supabase.com/docs/guides/functions/quickstart) (for example `hono-backend`) with `supabase functions new hono-backend`. + 3. Inside the generated function (for example `supabase/functions/hono-backend/index.ts`), set up a basic Hono app and export it via `Deno.serve(app.fetch)`: + + ```ts + import { Hono } from "jsr:@hono/hono"; + + // change this to your function name + const functionName = "hono-backend"; + const app = new Hono().basePath(`/${functionName}`); + + app.get("/hello", (c) => c.text("Hello from hono-server!")); + + Deno.serve(app.fetch); + ``` + + 4. Run the function locally with `supabase start` and `supabase functions serve --no-verify-jwt`, then call it from your TurboStarter app using the local or deployed function URL. + 5. When you're ready, deploy the function with `supabase functions deploy` (or `supabase functions deploy hono-backend`) and manage it using the Supabase dashboard, as described in the [Supabase Edge Functions docs](https://supabase.com/docs/guides/functions). + + This is entirely optional, but it's a great fit for lightweight APIs, webhooks, and other serverless logic you want to run alongside your Supabase project. + + + + ## Explore additional Supabase features + + Supabase is a full Postgres development platform, so beyond the database and storage pieces wired up above you can gradually add more features as your app grows ([see the Supabase homepage](https://supabase.com/) for an overview). + + Some features that fit especially well with TurboStarter's design are: + + * [Realtime](https://supabase.com/docs/guides/realtime) - built on [Postgres replication](https://www.postgresql.org/docs/current/runtime-config-replication.html), so you can stream changes from your existing TurboStarter tables (inserts, updates, deletes) into live UIs without changing how you manage schema or RLS. You still define tables and policies via `@turbostarter/db`, and opt into Realtime on top. + * [Vector](https://supabase.com/docs/guides/vector) - powered by the [pgvector](https://github.com/pgvector/pgvector) extension and stored in regular Postgres tables, making it easy to integrate semantic search or AI features while keeping everything in the same migrations and Drizzle models you already use in TurboStarter. We're using it extensively in our dedicated [AI Kit](/ai). + * [Cron](https://supabase.com/docs/guides/functions/cron) - enables you to schedule background jobs and periodic tasks with [pg\_cron](https://github.com/citusdata/pg_cron). You can define cron jobs for things like scheduled database cleanups, sending emails, report generation, or any recurring logic, all managed alongside your TurboStarter app with full Postgres integration. + + Because these features are all layered on top of Postgres, you can introduce them incrementally and keep managing everything through your familiar workflow. + + + + ## Start the development server + + With the database and other services configured to use Supabase, you can start TurboStarter as usual from the monorepo root: + + ```bash + pnpm dev + ``` + + TurboStarter will now: + + * Use **Supabase Postgres** as your database through `DATABASE_URL` + * Use **Supabase Storage** as your file storage through the S3-compatible endpoint + * Leverage **Supabase Edge Functions** (for example, with Hono) for your serverless backend + + + +That's it! You can now start building your application with Supabase as your main provider. Explore the [Supabase documentation](https://supabase.com/docs) for more features and best practices. + + +--- +url: /docs/extension/stack +title: Tech Stack +description: A detailed look at the technical details. +--- + +## Turborepo + +[Turborepo](https://turbo.build/) is a monorepo tool that helps you manage your project's dependencies and scripts. We chose a monorepo setup to make it easier to manage the structure of different features and enable code sharing between different packages. + +} /> + +## WXT (Vite) + +> It's like Next.js for browser extensions. + +[WXT](https://www.wxt.dev/) is a very lightweight and powerful framework (based on [Vite](https://vite.dev/)) for building browser extensions using most popular frontend tools. It provides a modern development experience with features like hot module reloading, TypeScript support, and automatic manifest generation. + +WXT simplifies the process of creating cross-browser extensions, allowing you to focus on your extension's functionality rather than boilerplate setup. + + + } /> + + } /> + + +## React + +[React](https://reactjs.org/) is a JavaScript library for building user interfaces. It's the core technology we use for creating the UI of our browser extension, allowing for efficient updates and rendering of components. + +} /> + +## Tailwind CSS + +[Tailwind CSS](https://tailwindcss.com) is a utility-first CSS framework that helps you build custom designs without writing any CSS. We also use [Radix UI](https://radix-ui.com) for our headless components library and [shadcn/ui](https://ui.shadcn.com) which enables you to generate pre-designed components with a single command. + + + } /> + + } /> + + } /> + + +## Hono & React Query + +[Hono](https://hono.dev) is a small, simple, and ultrafast web framework for the edge. It provides tools to help you build APIs and web applications faster. It includes an RPC client for making type-safe function calls from the frontend. We use Hono to build our serverless API endpoints. + +To make data fetching and caching from our API easy and reliable, we pair Hono with [React Query](https://tanstack.com/query/latest). It helps manage asynchronous data, caching, and state synchronization between the client and backend, delivering a fast and seamless UX. + + + } /> + + + } + /> + + +## Better Auth + +[Better Auth](https://www.better-auth.com) is a modern authentication library for fullstack applications. It provides ready-to-use snippets for features like email/password login, magic links, OAuth providers, and more. We use Better Auth to handle all authentication flows in our application. + +} /> + +## Drizzle + +[Drizzle](https://orm.drizzle.team/) is a super fast [ORM](https://orm.drizzle.team/docs/overview) (Object-Relational Mapping) tool for databases. It helps manage databases, generate TypeScript types from your schema, and run queries in a fully type-safe way. + +We use [PostgreSQL](https://www.postgresql.org) as our default database, but thanks to Drizzle's flexibility, you can easily switch to MySQL, SQLite or any [other supported database](https://orm.drizzle.team/docs/connect-overview) by updating a few configuration lines. + + + } /> + + } /> + + + +--- +url: /docs/extension/structure/background +title: Background service worker +description: Configure your extension's background service worker. +--- + +An extension's service worker is a powerful script that runs in the background, separate from other parts of the extension. It's loaded when it is needed, and unloaded when it goes dormant. + +Once loaded, an extension service worker generally runs as long as it is actively receiving events, though it [can shut down](https://developer.chrome.com/docs/extensions/develop/concepts/service-workers/lifecycle#idle-shutdown). Like its web counterpart, an extension service worker cannot access the DOM, though you can use it if needed with [offscreen documents](https://developer.chrome.com/docs/extensions/reference/api/offscreen). + +Extension service workers are more than network proxies (as web service workers are often described), they run in a separate [service worker context](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers). For example, when in this context, you no longer need to worry about CORS and can fetch resources from any origin. + +In addition to the [standard service worker events](https://developer.mozilla.org/docs/Web/API/ServiceWorkerGlobalScope#events), they also respond to extension events such as navigating to a new page, clicking a notification, or closing a tab. They're also registered and updated differently from web service workers. + +**It's common to offload heavy computation to the background service worker**, so you should always try to do resouce-expensive operations there and send results using [Messages API](/docs/extension/structure/messaging) to other parts of the extension. + +Code for the background service worker is located at `src/app/background` directory - you need to use `defineBackground` within `index.ts` file inside to allow WXT to include your script in the build. + +```ts title="src/app/background/index.ts" +import { defineBackground } from "wxt/sandbox"; + +const main = () => { + console.log( + "Background service worker is running! Edit `src/app/background` and save to reload.", + ); +}; + +export default defineBackground(main); +``` + +To see the service worker in action, reload the extension, then open its "Service Worker inspector": + +![Service Worker inspector](/images/docs/extension/structure/sw-inspector.png) + +You should see what we've logged in the console: + +![Service Worker console](/images/docs/extension/structure/sw-log.png) + +To communicate with the service worker from other parts of the extension, you can use the [Messaging API](/docs/extension/structure/messaging). + +## Persisting state + + + Service workers in `dev` mode always remain in `active` state. + + +The worker becomes idle after a few seconds of inactivity, and the browser will kill its process entirely after 5 minutes. This means all state (variables, etc.) is lost unless you use a storage engine. + +The simplest way to persist your background service worker's state is to use the [storage API](/docs/extension/structure/storage). + +The more advanced way is to send the state to a remote database via our [backend API](/docs/extension/api/overview). + + + + + + + + + + + + +--- +url: /docs/extension/structure/content-scripts +title: Content scripts +description: Learn more about content scripts. +--- + +Content scripts run in the context of web pages in an isolated world. This allows multiple content scripts from various extensions to coexist without conflicting with each other's execution and to stay isolated from the page's JavaScript. + +A script that ends with `.ts` will not have front-end runtime (e.g. react) bundled with it and won't be treated as a ui script, while a script that ends in `.tsx` will be. + +There are many use cases for content scripts: + +* Injecting a custom stylesheet into the page +* Scraping data from the current web page +* Selecting, finding, and styling elements from the current web page +* Injecting UI elements into current web page + +Code for the content scripts is located in `src/app/content` directory - you need to define `.ts` or `.tsx` file inside and use `defineContentScript` to allow WXT to include your script in the build. + +```ts title="src/app/content/index.ts" +export default defineContentScript({ + matches: [""], + async main(ctx) { + console.log( + "Content script is running! Edit `app/content` and save to reload.", + ); + }, +}); +``` + +Reload your extension, open a web page, then open its inspector: + +![Content Script](/images/docs/extension/structure/content-script.png) +To learn more about content scripts, e.g. how to configure only specific pages to load content scripts, how to inject them into `window` object or how to fetch data inside, please check [the official documentation](https://wxt.dev/guide/essentials/content-scripts.html). + +## UI scripts + +WXT has first-class support for mounting React components into the current webpage. This feature is called content scripts UI (CSUI). + +![CSUI](/images/docs/extension/structure/csui.png) + +An extension can have as many CSUI as needed, with each CSUI targeting a group of webpages or a specific webpage. + +To get started with CSUI, create a `.tsx` file in `src/app/content` directory and use `defineContentScript` allow WXT to include your script in the build and mount your component into the current webpage: + +```tsx title="src/app/content/index.tsx" +const ContentScriptUI = () => { + return ( + + ); +}; + +export default defineContentScript({ + matches: [""], + cssInjectionMode: "ui", + async main(ctx) { + const ui = await createShadowRootUi(ctx, { + name: "turbostarter-extension", + position: "overlay", + anchor: "body", + onMount: (container) => { + const app = document.createElement("div"); + container.append(app); + + const root = ReactDOM.createRoot(app); + root.render(); + return root; + }, + onRemove: (root) => { + root?.unmount(); + }, + }); + + ui.mount(); + }, +}); +export default ContentScriptUI; +``` + + + The `.tsx` extension is essential to differentiate between Content Scripts UI and regular Content Scripts. Make sure to check if you're using appropriate type of content script for your use case. + + +To learn more about content scripts UI, e.g. how to inject custom styles, fonts or the whole lifecycle of a component, please check [the official documentation](https://wxt.dev/guide/essentials/content-scripts.html#ui). + + + Under the hood, the component is wrapped inside the component that implements the Shadow DOM technique, together with many helpful features. This isolation technique prevents the web page's style from affecting your component's styling and vice-versa. + + [Read more about the lifecycle of CSUI](https://docs.plasmo.com/framework/content-scripts-ui/life-cycle) + + + +--- +url: /docs/extension/structure/messaging +title: Messaging +description: Communicate between your extension's components. +--- + +Messaging API makes communication between different parts of your extension easy. To make it simple and scalable, we're leveraging `@webext-core/messaging` library. + +It provides a declarative, type-safe, functional, promise-based API for sending, relaying, and receiving messages between your extension components. + +## Handling messages + +Based on our convention, we implemented a little abstraction on top of `@webext-core/messaging` to make it easier to use. That's why all types and keys are stored inside `lib/messaging` directory: + +```ts title="lib/messaging/index.ts" +import { defineExtensionMessaging } from "@webext-core/messaging"; + +export const Message = { + HELLO: "hello", +} as const; + +export type Message = (typeof Message)[keyof typeof Message]; + +interface Messages { + [Message.HELLO]: (message: string) => string; +} + +export const { onMessage, sendMessage } = defineExtensionMessaging(); +``` + +There you need to define what will be handled under each key. To make it more secure, only `Message` enum and `onMessage` and `sendMessage` functions are exported from the module. + +All message handlers are located in `src/app/background/messaging` directory under respective subdirectories. + +To create a message handler, create a TypeScript module in the `background/messaging` directory. Then, include your handlers for all keys related to the message: + +```ts title="app/background/messaging/hello.ts" +import { onMessage, Message } from "~/lib/messaging"; + +onMessage(Message.HELLO, (req) => { + const result = await querySomeApi(req.body.id); + + return result; +}); +``` + + + To make your handlers available across your extension, you need to import them + in the `background/index.ts` file. That way they could be interpreted by the + build process facilitated by WXT. + + +## Sending messages + +Extension pages, content scripts, or tab pages can send messages to the handlers using the `sendMessage` function. Since we orchestrate your handlers behind the scenes, the message names are typed and will enable autocompletion in your editor: + +```tsx title="app/popup/index.tsx" +import { sendMessage, Message } from "~/lib/messaging"; + +... + +const response = await sendMessage(Message.HELLO, "Hello, world!"); + +console.log(response); + +... +``` + +As it's an asynchronous operation, it's advisable to use [@tanstack/react-query](https://tanstack.com/query/latest/docs/framework/react/overview) integration to handle the response on the client side. + +We're already doing it that way when fetching auth session in the `User` component: + +```tsx title="hello.tsx" +export const Hello = () => { + const { data, isLoading } = useQuery({ + queryKey: [Message.HELLO], + queryFn: () => sendMessage(Message.HELLO, "Hello, world!"), + }); + + if (isLoading) { + return

Loading...

; + } + + /* do something with the data... */ + return

{data?.message}

; +}; +``` + + + + + + + + +--- +url: /docs/extension/structure/overview +title: Overview +description: Learn about the structure of the extension app. +--- + +Every browser extension is different and can include different parts, removing the ones that are not needed. + +TurboStarter ships with all the things you need to start developing your own extension including: + +* **Popup window** - a small window that appears when the user clicks the extension icon. +* **Options page** - a page that appears when user enters extension settings. +* **Side panel** - a panel that appears when the user clicks sidepanel. +* **New tab page** - a page that appears when the user opens a new tab. +* **Devtools page** - a page that appears when the user opens the browser's devtools. +* **Tab pages** - custom pages shipped with the extension. +* **Content scripts** - injected scripts that run in the browser page. +* **Background scripts** - scripts that run in the background. +* **Message passing** - a way to communicate between different parts of the extension. +* **Storage** - a way to store data in the extension. + +All the entrypoints are defined in `apps/extension/src/app` directory (it's similar to file-based routing in Next.js and Expo). + +This directory acts as a source for WXT framework which is used to build the extension. It has the following structure: + + + + + + + + + + + + + + + + + + + + + +By structurizing it this way, we can easily add new entrypoints in the future and extend rest of the extension independently from each other. + +We'll go through each part and explain the purpose of it, check following sections for more details: + + +--- +url: /docs/extension/structure/pages +title: Pages +description: Get started with your extension's pages. +--- + +Extension pages are built-in pages recognized by the browser. They include the extension's popup, options, sidepanel and newtab pages. + + + As WXT is based on Vite, it has very powerful [HMR support](https://vite.dev/guide/features#hot-module-replacement). This means that you don't need to refresh the extension manually when you make changes to the code. + + +## Popup + +The popup page is a small dialog window that opens when a user clicks on the extension's icon in the browser toolbar. It is the most common type of extension page. + +![Popup window](/images/docs/extension/structure/popup.png) + + + + + + + +## Options + +The options page is meant to be a dedicated place for the extension's settings and configuration. + +![Options page](/images/docs/extension/structure/options.png) + + + +## Devtools + +The devtools page is a custom page (including panels) that opens when a user opens the extension's devtools panel. + +![Devtools page](/images/docs/extension/structure/devtools.png) + + + +## New tab + +The new tab page is a custom page that opens when a user opens a new tab in the browser. + +![New tab page](/images/docs/extension/structure/newtab.png) + + + +## Side panel + +The side panel is a custom page that opens when a user clicks on the extension's icon in the browser toolbar. + +![Side panel](/images/docs/extension/structure/sidepanel.png) + + + +## Tabs + +Unlike traditional extension pages, tab (unlisted) pages are just regular web pages shipped with your extension bundle. Extensions generally redirect to or open these pages programmatically, but you can link to them as well. + +They could be useful for following cases: + +* when you want to show a some page when user first installs your extension +* when you want to have dedicated pages for authentication +* when you need more advanced routing setup + +![Tab page](/images/docs/extension/structure/tabs.png) + +Your tab page will be available under the `/tabs` path in the extension bundle. It will be accessible from the browser under the URL: + +``` +chrome-extension:///tabs/your-tab-page.html +``` + + + + +--- +url: /docs/extension/structure/storage +title: Storage +description: Learn how to store data in your extension. +--- + +TurboStarter leverages `wxt/storage` library to handle persistent storage for your extension. It's a utility library from that abstracts the persistent storage API available to browser extensions. + +It falls back to localStorage when the extension storage API is unavailable, allowing for state sync between extension pages, content scripts, background service workers and web pages. + + + To use the `wxt/storage` API, the "storage" permission **must** be added to the manifest: + + ```ts title="wxt.config.ts" + export default defineConfig({ + manifest: { + permissions: ["storage"], + }, + }); + ``` + + +## Storing data + +The base Storage API is designed to be easy to use. It is usable in every extension runtime such as background service workers, content scripts and extension pages. + +TurboStarter ships with predefined storage used to handle [theming](/docs/extension/customization/styling) in your extension, but you can create your own storage as well. + +All storage-related methods and types are located in `lib/storage` directory. + +```ts title="lib/storage/index.ts" +export const StorageKey = { + THEME: "local:theme", +} as const; + +export type StorageKey = (typeof StorageKey)[keyof typeof StorageKey]; +``` + +Then, to make it available around your extension, we're setting it up and providing default values: + +```ts title="lib/storage/index.ts" +import { storage as browserStorage } from "wxt/storage"; + +import { appConfig } from "~/config/app"; + +import type { ThemeConfig } from "@turbostarter/ui"; + +const storage = { + [StorageKey.THEME]: browserStorage.defineItem(StorageKey.THEME, { + fallback: appConfig.theme, + }), +} as const; +``` + +To learn more about customizing your storage, syncing state or setup automatic backups please refer to the [official documentation](https://wxt.dev/storage.html). + +## Consuming storage + +To consume storage in your extension, you can use the `useStorage` React hook that is automatically provided to every part of the extension. The hook API is designed to streamline the state-syncing workflow between the different pieces of an extension. + +Here is an example on how to consume our theme storage in `Layout` component: + +```tsx title="modules/common/layout/layout.tsx" +import { StorageKey, useStorage } from "~/lib/storage"; + +export const Layout = ({ children }: { children: React.ReactNode }) => { + const { data } = useStorage(StorageKey.THEME); + + return ( +
+ {children} +
+ ); +}; +``` + +Congrats! You've just learned how to persist and consume global data in your extension 🎉 + +For more advanced use cases, please refer to the [official documentation](https://wxt.dev/storage.html). + +### Usage with Firefox + +To use the storage API on Firefox during development you need to add an addon ID to your manifest, otherwise, you will get this error: + +> Error: The storage API will not work with a temporary addon ID. Please add an explicit addon ID to your manifest. For more information see [https://mzl.la/3lPk1aE](https://mzl.la/3lPk1aE) + +To add an addon ID to your manifest, add this to your package.json: + +```ts title="wxt.config.ts" +export default defineConfig({ + manifest: { + browser_specific_settings: { + gecko: { + id: "your-id@example.com", + }, + }, + }, +}); +``` + +During development, you may use any ID. If you have published your extension, you need to use the ID assigned by [Firefox Add-ons](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons). + + + + + + + + +--- +url: /docs/extension/tests/e2e +title: E2E tests +description: Simulate real user scenarios across the entire stack with automated end-to-end test tools and examples. +--- + + + End-to-end (E2E) tests will be available soon, allowing you to automate testing of real user flows and interactions across your application. + + Stay tuned for updates as we roll out robust E2E testing resources and examples. + + [See roadmap](https://github.com/orgs/turbostarter/projects/1) + + + +--- +url: /docs/extension/tests/unit +title: Unit tests +description: Write and run fast unit tests for individual functions and components with instant feedback. +--- + +Unit tests are a type of automated test where individual units or components are tested. The "unit" in "unit test" refers to the smallest testable parts of an application. These tests are designed to verify that each unit of code performs as expected. + +TurboStarter uses [Vitest](https://vitest.dev) as the unit testing framework. It's a blazing-fast test runner built on top of [Vite](https://vitejs.dev), designed for modern JavaScript and TypeScript projects. + + + If you've used [Jest](https://jestjs.io) before, you already know Vitest - it shares the same API. But Vitest is built for speed: native TypeScript support without transpilation, parallel test execution, and a smart watch mode that only re-runs tests affected by your changes. + + It comes with everything you need out of the box - code coverage, snapshot testing, mocking, and a slick UI for debugging. Fast feedback, zero configuration. + + +## Why write unit tests? + +Unit tests give you **fast, focused feedback** on small pieces of your code - individual functions, hooks, or components. Instead of debugging an entire page or flow, you can verify just the logic you care about in isolation. + +They also act as **living documentation**: a good test tells you how a function is supposed to behave, which edge cases are important, and what assumptions the code makes. This makes it much easier to safely refactor or extend features later. + +In TurboStarter, unit tests are designed to be **cheap and quick to run**, so you can keep Vitest running in watch mode while you code. Every change you make is immediately checked, helping you catch regressions before they ever reach integration or end‑to‑end tests. + +## Configuration + +TurboStarter configures Vitest to be **as simple as possible**, while still taking advantage of [Turborepo's caching](https://turborepo.com/docs/crafting-your-repository/caching) and Vitest's [Test Projects](https://vitest.dev/guide/projects). + +```ts title="vitest.config.ts" +import { mergeConfig } from "vitest/config"; + +import baseConfig from "@turbostarter/vitest-config/base"; + +export default mergeConfig(baseConfig, { + test: { + /* your extended test configuration here */ + }, +}); +``` + +* **Per-package tests**: each package that has unit tests defines its own `test` script. This keeps the configuration close to the code and makes it easy to add tests to any workspace. +* **Turbo tasks for CI**: the root `test` task (`pnpm test`) uses `turbo run test` to execute all package-level test scripts with smart caching, which is ideal for CI pipelines where you want to avoid re-running unchanged tests. +* **Vitest Test Projects for local dev**: a root Vitest configuration uses [Test Projects](https://vitest.dev/guide/projects) to run all unit test suites from a single command, which is perfect for local development when you want fast feedback across the whole monorepo. + +This **hybrid setup** combines Turborepo and Vitest Projects in a way that fits TurboStarter's principles: cached, package-aware runs in CI, and a single, unified Vitest entry point for local development. + +You can read more about this setup in the official documentation guides listed below. + + + + + + + +## Running tests + +There are a few different ways to run unit tests, depending on what you're doing: + +* **CI / full test run** - at the root of the repo: + +```bash +pnpm test +``` + +This runs `turbo run test`, which executes all `test` scripts in packages that define them, with Turborepo handling caching so unchanged packages are skipped. This is what you should use in your CI/CD pipeline. + +* **One-off local run with Vitest Projects**: + +```bash +pnpm test:projects +``` + +This uses Vitest [Test Projects](https://vitest.dev/guide/projects) to run all configured unit test suites from a single command, which is great when you want to quickly validate the whole monorepo locally. + +* **Watch mode during development**: + +```bash +pnpm test:projects:watch +``` + +This starts Vitest in watch mode across all Test Projects. As you edit files, only the affected tests are re-run, giving you fast feedback while you work. + +## Code coverage + +Unit test coverage helps you understand **how much** of your code is being tested. While it can't guarantee bug-free code, it shines a light on untested paths that could hide issues or regressions. + +To generate a code coverage report for all unit tests, run: + +```bash +pnpm turbo test:coverage +``` + +This command runs the coverage task across all relevant packages (using Turborepo) and collects the results into a single coverage output. + +To open the coverage report in your browser: + +```bash +pnpm turbo test:coverage:view +``` + +This will build the HTML report and launch it using your default browser, so you can explore which files and branches are covered. + + + You can also store the generated coverage report as a [GitHub Actions artifact](https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts) during your CI/CD pipeline, just add the following steps to your workflow job: + + ```yaml title=".github/workflows/ci.yml" + # your workflow job configuration here + + - name: 📊 Generate coverage + run: pnpm turbo test:coverage + + - name: 🗃️ Archive coverage report + uses: actions/upload-artifact@v5 + with: + name: coverage-${{ github.sha }} + path: tooling/vitest/coverage/report + ``` + + This will generate a test coverage report and upload it as an artifact, so you can access it from GitHub Actions tab for later inspection. + + +A high coverage percentage means your tests execute most lines and branches - but the quality and relevance of your tests matter more than the raw number. Use coverage reports to spot gaps and guide improvements, not as the sole metric of test health. + +![Code coverage](/images/docs/code-coverage.png) + +## Best practices + +Unit tests should work **for you**, not the other way around. Focus on writing tests that make it easier to change code with confidence, not on satisfying arbitrary rules or reaching a magic number in a dashboard. + +Code coverage is a **useful metric**, but it **SHOULD NOT** be the goal. It's better to have a smaller set of high‑value tests that cover critical paths and edge cases than a huge suite of fragile tests that are hard to maintain. + +When in doubt, ask: *“Does this test give **me** confidence that I can change this code without breaking users?”* If the answer is no, refactor or remove it. + +Finally, keep unit tests focused on **small, isolated pieces of logic**. More advanced flows — like multi-step user journeys, cross-service interactions, or full-page behavior — are better covered by [end-to-end (E2E) tests](/docs/web/tests/e2e), where you can verify the system as a whole. + + +--- +url: /docs/extension/troubleshooting/installation +title: Installation +description: Find answers to common extension installation issues. +--- + +## Cannot clone the repository + +Issues related to cloning the repository are usually related to a Git misconfiguration in your local machine. The commands displayed in this guide using SSH: these will work only if you have setup your SSH keys in Github. + +If you run into issues, [please make sure you follow this guide to set up your SSH key in Github.](https://docs.github.com/en/authentication/connecting-to-github-with-ssh) + +If this also fails, please use HTTPS instead. You will be able to see the commands in the repository's Github page under the "Clone" dropdown. + +Please also make sure that the account that accepted the invite to TurboStarter, and the locally connected account are the same. + +## Local database doesn't start + +If you cannot run the local database container, it's likely you have not started [Docker](https://docs.docker.com/get-docker/) locally. Our local database requires Docker to be installed and running. + +Please make sure you have installed Docker (or compatible software such as [Colima](https://github.com/abiosoft/colima), [Orbstack](https://github.com/orbstack/orbstack)) and that is running on your local machine. + +Also, make sure that you have enough [memory and CPU allocated](https://docs.docker.com/engine/containers/resource_constraints/) to your Docker instance. + +## Permissions issues + +If some feature of your extension is not working, it's possible that you're missing a permission in the manifest config. + +Make sure to check the [permissions](/docs/extension/configuration/manifest#overriding-manifest) section in the manifest config file. + +## I don't see my translations + +If you don't see your translations appearing in the application, there are a few common causes: + +1. Check that your translation `.json` files are properly formatted and located in the correct directory +2. Verify that the language codes in your configuration match your translation files +3. Enable debug mode (`debug: true`) in your i18next configuration to see detailed logs + +[Read more about configuration for translations](/docs/extension/internationalization#configuration) + +## "Module not found" error + +This issue is mostly related to either dependency installed in the wrong package or issues with the file system. + +The most common cause is incorrect dependency installation. Here's how to fix it: + +1. Clean the workspace: + + ```bash + pnpm clean + ``` + +2. Reinstall the dependencies: + ```bash + pnpm i + ``` + +If you're adding new dependencies, make sure to install them in the correct package: + +```bash +# For main app dependencies +pnpm install --filter mobile my-package + +# For a specific package +pnpm install --filter @turbostarter/ui my-package +``` + +If the issue persists, please check the file system for any issues. + +### Windows OneDrive + +OneDrive can cause file system issues with Node.js projects due to its file syncing behavior. If you're using Windows with OneDrive, you have two options to resolve this: + +1. Move your project to a location outside of OneDrive-synced folders (recommended) +2. Disable OneDrive sync specifically for your development folder + +This prevents file watching and symlink issues that can occur when OneDrive tries to sync Node.js project files. + + +--- +url: /docs/extension/troubleshooting/publishing +title: Publishing +description: Find answers to common publishing issues. +--- + +## My extension submission was rejected + +If your extension submission was rejected, you probably got an email with the reason. You'll need to fix the issues and upload a new build of your extension to the store and send it for review again. + +Make sure to follow the [guidelines](/docs/extension/marketing) when submitting your extension to ensure that everything is setup correctly. + +## Version number mismatch + +If you get version number conflicts when submitting: + +1. Ensure your `manifest.json` version matches what's in the store +2. Increment the version number appropriately for each new submission +3. Make sure the version follows semantic versioning (e.g., `1.0.1`) + +## Missing permissions in manifest + +If your extension is rejected due to permission issues: + +1. Review the permissions declared in your `manifest.json` +2. Ensure all permissions are properly justified in your submission +3. Remove any unused permissions that aren't essential +4. Consider using optional permissions where possible + +[Learn more about permissions](/docs/extension/configuration/manifest#permissions) + +## Content Security Policy (CSP) violations + +If your extension is rejected due to CSP issues: + +1. Check your manifest's `content_security_policy` field +2. Ensure all external resources are properly whitelisted +3. Remove any unsafe inline scripts or eval usage +4. Use more secure alternatives like `browser.scripting.executeScript` + +## My extension crashes on production build + +If the extension works during development but crashes after publishing or when loaded unpacked in production mode, check these common causes: + +1. **Uncaught runtime errors** in the background service worker or content scripts. Open `chrome://extensions` (or `about:debugging` in Firefox) → enable Developer mode → Inspect the service worker/content script and check the console for stack traces. +2. **Missing permissions or host permissions** causing APIs to throw (e.g., network calls, tabs access). Ensure required `permissions` and `host_permissions` are declared in `manifest.json`. +3. **CSP blocking resources** (inline scripts/styles, remote fonts, or endpoints). Verify `content_security_policy` and update code to avoid unsafe patterns. +4. **Missing assets or incorrect paths** referenced in `manifest.json` (`icons`, `web_accessible_resources`, `action.default_popup`, etc.). Confirm files exist in the final build output and paths match. +5. **Build-time variables not resolved**. If you rely on environment variables, ensure they’re inlined at build time or have safe fallbacks at runtime. Example: + ```js + const apiUrl = env.VITE_SITE_URL ?? "https://api.example.com"; + ``` +6. **Module format or bundler config issues** (MV3 service worker must be ESM if `type: 'module'`). Align bundler output with your manifest expectations and rebuild. + +Try this: + +1. Reproduce with a production bundle locally and load it as an unpacked extension; inspect background and content script logs for errors. +2. Validate `manifest.json` and ensure all referenced files are present in the build output. +3. Temporarily relax CSP locally to confirm whether CSP is the cause; then apply a compliant fix (don’t ship relaxed CSP). +4. Add fallbacks for any build-time variables and rebuild. + + +--- +url: /docs/mobile/ai +title: AI +description: Learn how to use AI integration in your mobile app. +--- + +As AI integration for [web](/docs/web/ai/overview), [extension](/docs/extension/ai), and mobile is based on the same battle-tested [Vercel AI SDK](https://sdk.vercel.ai/docs/introduction), the implementation is very similar across platforms. + +In this section, we'll focus on how to consume AI responses in the mobile app. For server-side implementation details, please refer to the [web documentation](/docs/web/ai/overview). + +## Features + +The most common AI integration features are also supported in the mobile app: + +* **Chat**: Build chat interfaces inside native mobile apps. +* **Streaming**: Receive AI responses as soon as the model starts generating them, without waiting for the full response to be completed. +* **Image generation**: Generate images based on a given prompt. + +You can easily compose your application using these building blocks or extend them to suit your specific needs. + +## Usage + +The usage of AI integration in the mobile app is the same as for [web app](/docs/web/ai/configuration#client-side) and [browser extension](/docs/extension/ai#server--client). We use the exact same [API endpoint](/docs/web/ai/configuration#api-endpoint), and since TurboStarter ships with built-in support for streaming on mobile, we can leverage it to display answers incrementally to the user as they're generated. + +```tsx title="ai.tsx" +import { useChat } from "@ai-sdk/react"; +import { DefaultChatTransport } from "ai"; + +const AI = () => { + const { messages } = useChat({ + transport: new DefaultChatTransport({ + api: "/api/ai/chat", + }), + }); + + return ( + + {messages.map((message) => ( + + {message.parts.map((part, i) => { + switch (part.type) { + case "text": + return {part.text}; + } + })} + + ))} + + ); +}; + +export default AI; +``` + +By leveraging this integration, we can easily manage the state of the AI request and update the UI as soon as the response is ready. + +TurboStarter ships with a ready-to-use implementation of AI chat, allowing you to see this solution in action. Feel free to reuse or modify it according to your needs. + + +--- +url: /docs/mobile/analytics/configuration +title: Configuration +description: Learn how to configure mobile analytics in TurboStarter. +--- + +The `@turbostarter/analytics-mobile` package offers a streamlined and flexible approach to tracking events in your TurboStarter mobile app using various analytics providers. It abstracts the complexities of different analytics services and provides a consistent interface for event tracking. + +In this section, we'll guide you through the configuration process for each supported provider. + +Note that the configuration is validated against a schema, so you'll see error messages in the console if anything is misconfigured. + +## Permissions + +First and foremost, to start tracking any metrics from your app (and to do so legally), you need to ask your users for permission. It's [required](https://support.apple.com/en-us/102420), and you're not allowed to collect any data without it. + +To make this process as simple as possible, TurboStarter comes with a `useTrackingPermissions` hook that you can use to access the user's consent status. It will handle asking for permission automatically as well as process updates made through the general phone settings. + +```tsx +import { useTrackingPermissions } from "@turbostarter/analytics-mobile"; + +export const MyComponent = () => { + const granted = useTrackingPermissions(); + + if (granted) { + // Start tracking + } else { + // Disable tracking + } +}; +``` + +Also, for Apple, you must declare the tracking justification via [App Tracking Transparency](https://developer.apple.com/documentation/apptrackingtransparency). It comes pre-configured in TurboStarter via the [Expo Config Plugin](https://docs.expo.dev/versions/latest/config/app/#plugins), where you can provide a custom message to the user: + +```ts title="app.config.ts" +export default ({ config }: ConfigContext): ExpoConfig => ({ + ...config, + plugins: [ + [ + "expo-tracking-transparency", + { + /* 🍎 Describe why you need access to the user's data */ + userTrackingPermission: + "This identifier will be used to deliver personalized ads to you.", + }, + ], + ], +}); +``` + +This way, we ensure that the user is aware of the data we collect and can make an informed decision. If you don't provide this information, your app is likely to be rejected by Apple and/or Google during the [review process](/docs/mobile/publishing/checklist#send-to-review). + +## Providers + +TurboStarter supports multiple analytics providers, each with its own unique configuration. Below, you'll find detailed information on how to set up and use each supported provider. Choose the one that best suits your needs and follow the instructions in the respective accordion section. + + + + To use Google Analytics as your analytics provider, you need to [configure and link a Firebase project to your app](/docs/mobile/installation/firebase). + + After that, you can proceed with the installation of the analytics package: + + ```bash + pnpm add --filter @turbostarter/analytics-mobile @react-native-firebase/analytics + ``` + + Also, make sure to activate the Google Analytics provider as your analytics provider by updating the exports in: + + ```ts title="index.ts" + // [!code word:google-analytics] + export * from "./google-analytics"; + export * from "./google-analytics/env"; + ``` + + To customize the provider, you can find its definition in `packages/analytics/mobile/src/providers/google-analytics` directory. + + For more information, please refer to the [React Native Firebase documentation](https://rnfirebase.io/analytics/usage). + + ![Google Analytics dashboard](/images/docs/web/analytics/google/dashboard.jpg) + + + + + PostHog is also one of pre-configured providers for [monitoring](/docs/mobile/monitoring/overview) in TurboStarter mobile apps. You can learn more about it [here](/docs/mobile/monitoring/posthog). + + + To use PostHog as your analytics provider, you need to configure a PostHog instance. You can obtain the [Cloud](https://app.posthog.com/signup) instance by [creating an account](https://app.posthog.com/signup) or [self-host](https://posthog.com/docs/self-host) it. + + Then, create a project and, based on your [project settings](https://app.posthog.com/project/settings), fill the following environment variables in your `.env.local` file in `apps/mobile` directory and your `eas.json` file: + + ```dotenv + EXPO_PUBLIC_POSTHOG_KEY="your-posthog-api-key" + EXPO_PUBLIC_POSTHOG_HOST="your-posthog-instance-host" + ``` + + Also, make sure to activate the PostHog provider as your analytics provider by updating the exports in: + + ```ts title="index.ts" + // [!code word:posthog] + export * from "./posthog"; + export * from "./posthog/env"; + ``` + + To customize the provider, you can find its definition in `packages/analytics/mobile/src/providers/posthog` directory. + + For more information, please refer to the [PostHog documentation](https://posthog.com/docs). + + ![PostHog dashboard](/images/docs/web/analytics/posthog.png) + + + + To use Mixpanel as your analytics provider, you need to [create an account](https://mixpanel.com/) and [obtain your project token](https://help.mixpanel.com/hc/en-us/articles/115004502806-Find-Project-Token). + + Then, set it as an environment variable in your `.env.local` file in the `apps/mobile` directory and your `eas.json` file: + + ```dotenv + EXPO_PUBLIC_MIXPANEL_TOKEN="your-project-token" + ``` + + Also, make sure to activate the Mixpanel provider as your analytics provider by updating the exports in: + + ```ts title="index.ts" + // [!code word:mixpanel] + export * from "./mixpanel"; + export * from "./mixpanel/env"; + ``` + + To customize the provider, you can find its definition in `packages/analytics/mobile/src/providers/mixpanel` directory. + + For more information, please refer to the [Mixpanel documentation](https://docs.mixpanel.com/). + + + +## Context + +To enable tracking events, capturing screen views and other analytics features, you need to wrap your app with the `Provider` component that's implemented by every provider and available through the `@turbostarter/analytics-mobile` package: + +```tsx title="providers.tsx" +// [!code word:AnalyticsProvider] +import { memo } from "react"; + +import { Provider as AnalyticsProvider } from "@turbostarter/analytics-mobile"; + +interface ProvidersProps { + readonly children: React.ReactNode; +} + +export const Providers = memo(({ children }) => { + return ( + + {children} + + ); +}); + +Providers.displayName = "Providers"; +``` + +By implementing this setup, you ensure that all analytics events are properly tracked from your mobile app code. This configuration allows you to safely utilize the [Analytics API](/docs/mobile/analytics/tracking) within your components, enabling comprehensive event tracking and data collection. + + +--- +url: /docs/mobile/analytics/overview +title: Overview +description: Get started with mobile analytics in TurboStarter. +--- + +When it comes to mobile app analytics, we can distinguish between two types: + +* **Store listing analytics**: Used to track the performance of your mobile app's store listing (e.g., how many people have viewed your app in the store or how many have installed it). +* **In-app analytics**: Tracks user actions within your mobile app (e.g., how many users entered a specific screen, how many users clicked on a specific button, etc.). + +The `@turbostarter/analytics-mobile` package provides a set of tools to easily implement both types of analytics in your mobile app. + +## Store listing analytics + +Interpreting your mobile app's store listing metrics can help you evaluate how changes to your app and store listing affect conversion rates. For example, you can identify keywords that users are searching for to optimize your app's store listing. + +While each store implements a different set of metrics, there are some common ones you should be aware of: + +* **Downloads**: The total number of times your app was downloaded, including both first-time downloads and re-downloads. +* **Sales**: The total number of pre-orders, first-time app downloads, in-app purchases, and their associated sales. +* **Usage**: A variety of user engagement metrics, such as installations, sessions, crashes, and active devices. + +To learn more about these or other metrics (e.g., how to create custom reports or KPIs), please refer to the official documentation of the store you're publishing to: + + + + + + + +## In-app analytics + +TurboStarter comes with built-in analytics support for multiple providers as well as a unified API for tracking events. This API enables you to easily and consistently track user behavior and app usage across your mobile application. + +To learn more about each provider and how to configure them, see their respective sections: + + + + + + + + + +All configuration and setup is built-in with a unified API, allowing you to switch between providers by simply changing the exports. You can even introduce your own provider without breaking any tracking-related logic. + +In the following sections, we'll cover how to set up each provider and how to track events in your application. + + +--- +url: /docs/mobile/analytics/tracking +title: Tracking events +description: Learn how to track events in your TurboStarter mobile app. +--- + +The strategy for tracking events that every provider has to implement is extremely simple: + +```ts +export type AllowedPropertyValues = string | number | boolean; + +type TrackFunction = ( + event: string, + data?: Record, +) => void; + +export interface AnalyticsProviderStrategy { + Provider: ({ children }: { children: React.ReactNode }) => React.ReactNode; + track: TrackFunction; +} +``` + + + You don't need to worry much about this implementation, as all the providers are already configured for you. However, it's useful to be aware of this structure if you plan to add your own custom provider. + + +As shown above, each provider must supply two key elements: + +1. `Provider` - a component that [wraps your app](/docs/mobile/analytics/configuration#context). +2. `track` - a function responsible for sending event data to the provider. + +To track an event, you simply need to invoke the `track` method, passing the event name and an optional data object: + +```tsx +import { track } from "@turbostarter/analytics-mobile"; + +export const MyComponent = () => { + return ( + track("button.click", { country: "US" })}> + Track event + + ); +}; +``` + +In most mobile apps, you'll only ever need to use the `track` method to track events. You can use it anywhere in your app code—such as in response to user interactions, navigation events, or custom actions - by simply calling `track` with an event name and optional properties. + +## Identifying users + +Linking events to specific users enables you to build a full picture of how they're using your product across different sessions, devices, and platforms. + +For identification purposes, we're extending the strategy with the `identify` and `reset` methods. They are optional and only needed if you want to identify users in your app and associate their actions with a specific user ID. + +```ts +type IdentifyFunction = ( + userId: string, + traits?: Record, +) => void; + +export interface AnalyticsProviderClientStrategy { + identify: IdentifyFunction; + reset: () => void; +} +``` + +To identify users, call the `identify` method, passing the user's ID and an optional traits object: + +```tsx +import { identify } from "@turbostarter/analytics-mobile"; + +identify("user-123", { name: "John Doe" }); +``` + +This will associate all future events with the user's ID, allowing you to track user behavior and gain valuable insights into your application's usage patterns. + + + The `identify` method is configured out-of-the-box to react on changes to the user's authentication state. + + When the user is authenticated, the `identify` method will be called with the user's ID and the user's traits. When the user is logged out, the `reset` method will be called to clear the existing user identification. + + +Congratulations! You've now mastered event tracking in your TurboStarter mobile app. With this knowledge, you're well-equipped to analyze user behaviors and gain valuable insights into your application's usage patterns. Happy analyzing! + + +--- +url: /docs/mobile/api/client +title: Using API client +description: How to use API client to interact with the API. +--- + +In mobile app code, you can only access the API client from the **client-side.** + +When you create a new component or screen and want to fetch some data, you can use the API client to do so. + +## Creating a client + +We're creating a client-side API client in `apps/mobile/src/lib/api/index.tsx` file. It's a simple wrapper around the [@tanstack/react-query](https://tanstack.com/query/latest/docs/framework/react/overview) that fetches or mutates data from the API. + +It also requires wrapping your app in a `QueryClientProvider` component to provide the API client to the rest of the app: + +```tsx title="_layout.tsx" +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + + ... + + ... + + + + + ); +} +``` + + + Inside the `apps/mobile/src/lib/api/utils.ts` file we're calling a function to get base url of your api, so make sure it's set correctly (especially on production) and your web api endpoint is corresponding with the name there. + + ```tsx title="utils.ts" + const getBaseUrl = () => { + /** + * Gets the IP address of your host-machine. If it cannot automatically find it, + * you'll have to manually set it. NOTE: Port 3000 should work for most but confirm + * you don't have anything else running on it, or you'd have to change it. + * + * **NOTE**: This is only for development. In production, you'll want to set the + * baseUrl to your production API URL. + */ + const debuggerHost = Constants.expoConfig?.hostUri; + const localhost = debuggerHost?.split(":")[0]; + + if (!localhost) { + console.warn("Failed to get localhost. Pointing to production server..."); + return env.EXPO_PUBLIC_SITE_URL; + } + return `http://${localhost}:3000`; + }; + ``` + + As you can see we're relying on your machine IP address for local development (in case you want to open the app from another device) or on the [environment variables](/docs/mobile/configuration/environment-variables) in production to get it, so there shouldn't be any issues with it, but in case, please be aware where to find it 😉 + + +## Queries + +Of course, everything comes already configured for you, so you just need to start using `api` in your components/screens. + +For example, to fetch the list of posts you can use the `useQuery` hook: + +```tsx title="app/(tabs)/tab-one.tsx" +import { api } from "~/lib/api"; + +export default function TabOneScreen() { + const { data: posts, isLoading } = useQuery({ + queryKey: ["posts"], + queryFn: async () => { + const response = await api.posts.$get(); + + if (!response.ok) { + throw new Error("Failed to fetch posts!"); + } + + return response.json(); + }, + }); + + if (isLoading) { + return Loading...; + } + + /* do something with the data... */ + return ( + + {JSON.stringify(posts)} + + ); +} +``` + +It's using the `@tanstack/react-query` [useQuery API](https://tanstack.com/query/latest/docs/framework/react/reference/useQuery), so you shouldn't have any troubles with it. + + + + + + + +## Mutations + +If you want to perform a mutation in your mobile code, you can use the `useMutation` hook that comes straight from the integration with [Tanstack Query](https://tanstack.com/query): + +```tsx title="form.tsx" +import { api } from "~/lib/api"; + +export function CreatePost() { + const queryClient = useQueryClient(); + const { mutate } = useMutation({ + mutationFn: async (post: PostInput) => { + const response = await api.posts.$post(post); + + if (!response.ok) { + throw new Error("Failed to create post!"); + }, + }, + onSuccess: () => { + toast.success("Post created successfully!"); + queryClient.invalidateQueries({ queryKey: ["posts"] }); + }, + }); + + return ( + + + + ); +} +``` + +Here, we're also invalidating the query after the mutation is successful. This is a very important step to make sure that the data is updated in the UI. + + + + + + + +## Handling responses + +As you can see in the examples above, the [Hono RPC](https://hono.dev/docs/guides/rpc) client returns a plain `Response` object, which you can use to get the data or handle errors. However, implementing this handling in every query or mutation can be tedious and will introduce unnecessary boilerplate in your codebase. + +That's why we've developed the `handle` function that unwraps the response for you, handles errors, and returns the data in a consistent format. You can safely use it with any procedure from the API client: + + + + ```tsx + // [!code word:handle] + import { handle } from "@turbostarter/api/utils"; + + import { api } from "~/lib/api"; + + export default function TabOneScreen() { + const { data: posts, isLoading } = useQuery({ + queryKey: ["posts"], + queryFn: handle(api.posts.$get), + }); + + if (isLoading) { + return Loading...; + } + + /* do something with the data... */ + return ( + + {JSON.stringify(posts)} + + ); + } + ``` + + + + ```tsx + // [!code word:handle] + import { handle } from "@turbostarter/api/utils"; + + import { api } from "~/lib/api/client"; + + export default function CreatePost() { + const queryClient = useQueryClient(); + const { mutate } = useMutation({ + mutationFn: handle(api.posts.$post), + onSuccess: () => { + toast.success("Post created successfully!"); + queryClient.invalidateQueries({ queryKey: ["posts"] }); + }, + }); + + return ( +
+ +
+ ); + } + ``` +
+
+ +With this approach, you can focus on the business logic instead of repeatedly writing code to handle API responses in your browser extension components, making your extension's codebase more readable and maintainable. + +The same error handling and response unwrapping benefits apply whether you're building web, mobile, or extension interfaces - allowing you to keep your data fetching logic consistent across all platforms. + + +--- +url: /docs/mobile/api/overview +title: Overview +description: Get started with the API. +--- + + + To enable communication between your Expo app and the server in a production environment, the web application with Hono API must be deployed first. + + + + + + + + +TurboStarter is designed to be a scalable and production-ready full-stack starter kit. One of its core features is a dedicated and extensible API layer. To enable this in a type-safe manner, we chose [Hono](https://hono.dev) as the API server and client library. + + + Hono is a small, simple, and ultrafast web framework that gives you a way to + define your API endpoints with full type safety. It provides built-in + middleware for common needs like validation, caching, and CORS. + + It also + includes an [RPC client](https://hono.dev/docs/guides/rpc) for making + type-safe function calls from the frontend. Being edge-first, it's optimized + for serverless environments and offers excellent performance. + + +All API endpoints and their resolvers are defined in the `packages/api/` package. Here you will find a `modules` folder that contains the different feature modules of the API. Each module has its own folder and exports all its resolvers. + +For each module, we create a separate Hono route in the `packages/api/index.ts` file and aggregate all sub-routers into one main router. + +The API is then exposed as a route handler that will be provided as a Next.js API route: + +```ts title="apps/web/src/app/api/[...route]/route.ts" +import { handle } from "hono/vercel"; + +import { appRouter } from "@turbostarter/api"; + +const handler = handle(appRouter); +export { + handler as GET, + handler as POST, + handler as OPTIONS, + handler as PUT, + handler as PATCH, + handler as DELETE, + handler as HEAD, +}; +``` + +Learn more about how to use the API in your mobile app in the following sections: + + +--- +url: /docs/mobile/auth/2fa +title: Two-Factor Authentication (2FA) +description: Add an extra layer of security with two-factor authentication in your mobile app. +--- + +TurboStarter uses [Better Auth's 2FA plugin](https://www.better-auth.com/docs/plugins/2fa) to provide multi-factor authentication (MFA) capabilities in your mobile app. Two-factor authentication adds an extra layer of security by requiring users to provide a second form of verification alongside their password. + +## Available methods + +TurboStarter supports multiple 2FA verification methods through Better Auth: + +* **TOTP (Time-based One-Time Password)** - codes generated by authenticator apps +* **OTP (One-Time Password)** - codes sent via email or SMS +* **Backup codes** - single-use recovery codes for account recovery + +You can use any TOTP-compatible authenticator app, such as: + +* [Google Authenticator](https://support.google.com/accounts/answer/1066447) +* [Authy](https://authy.com/) +* [Microsoft Authenticator](https://www.microsoft.com/en-us/security/mobile-authenticator-app) +* [1Password](https://1password.com/features/authenticator/) +* [Bitwarden](https://bitwarden.com/help/authenticator-keys/) + +## Enabling 2FA + + + + ### Enable in settings + + Users enable two-factor authentication in their account security settings within the mobile app. + + ![Enable 2FA](/images/docs/mobile/auth/two-factor/enable.png) + + + + ### Setup authenticator + + A QR code is displayed in the mobile app for users to scan with their authenticator app. Users can also manually enter the setup key if needed. + + ![Setup authenticator](/images/docs/mobile/auth/two-factor/authenticator-app.png) + + + + ### Verify setup + + Users enter a verification code from their authenticator to confirm setup directly in the mobile app. + + + + ### Backup codes + + Users receive single-use backup codes for account recovery, which can be saved or shared from the mobile app. + + ![Backup codes](/images/docs/mobile/auth/two-factor/backup-codes.png) + + + + + Recovery codes are essential for account recovery if users lose access to + their authenticator device. Make sure to educate users about safely storing + their backup codes, and consider providing options to save them to the device + or share them securely. + + +## Using 2FA + + + + ### Sign in normally + + Users enter their email and password or use other authentication methods (biometric, social login) as usual in the mobile app. + + + + ### 2FA prompt + + After successful password verification, users are prompted for their 2FA code in a native mobile interface. + + ![2FA prompt](/images/docs/mobile/auth/two-factor/sign-in-prompt.png) + + + + ### Enter verification code + + Users input the 6-digit code from their authenticator app using the mobile keyboard. + + + + ### Access granted + + Upon successful verification, users gain access to their account and are navigated to the main app screen. + + + +### Trusted devices + +Users can mark their mobile device as trusted during 2FA verification. Trusted devices won't require 2FA verification for 60 days, providing a balance between security and convenience. This is particularly useful for personal mobile devices. + +## Mobile-specific considerations + +### Biometric integration + +On mobile devices, 2FA can be enhanced with biometric authentication (fingerprint, face recognition) for added security and convenience. + +### App switching + +The mobile app should handle switching between your app and authenticator apps seamlessly, maintaining the authentication state when users return. + +### Offline support + +Consider implementing offline backup code verification for scenarios where users may have limited connectivity. + +### Push notifications + +For OTP delivery via SMS or email, ensure your app handles incoming notifications gracefully during the authentication flow. + +## Configuration + +2FA is configured through Better Auth's plugin system. The plugin handles: + +* Secure secret generation and storage +* QR code generation for authenticator setup +* TOTP code validation +* Backup code generation and management +* Trusted device management +* Mobile-specific session handling + +For detailed implementation instructions, refer to the [Better Auth 2FA documentation](https://www.better-auth.com/docs/plugins/2fa). + + +--- +url: /docs/mobile/auth/configuration +title: Configuration +description: Configure authentication for your application. +--- + +TurboStarter supports multiple different authentication methods: + +* **Password** - the traditional email/password method +* **Magic Link** - passwordless email link authentication +* **Anonymous** - guest mode for users who want to proceed anonymously +* **OAuth** - OAuth providers, [Apple](https://www.better-auth.com/docs/authentication/apple), [Google](https://www.better-auth.com/docs/authentication/google) and [Github](https://www.better-auth.com/docs/authentication/github) are set up by default + +All authentication methods are enabled by default, but you can easily customize them to your needs. You can enable or disable any method, and configure them according to your requirements. + + + Remember that you can mix and match these methods or add new ones - for + example, you can have both password and magic link authentication enabled at + the same time, giving your users more flexibility in how they authenticate. + + +Authentication configuration can be customized through a simple configuration file. The following sections explain the available options and how to configure each authentication method based on your requirements. + +## API + +To enable new authentication method or add some plugin, you'd need to update the API configuration. Please refer to [web authentication configuration](/docs/web/auth/configuration) for more information as it's not strictly related with mobile app configuration. + + + For mobile apps, we need to define an [authentication trusted origin](https://www.better-auth.com/docs/reference/security#trusted-origins) using a mobile app scheme instead. + + App schemes (like `turbostarter://`) are used for [deep linking](https://docs.expo.dev/guides/linking/) users to specific screens in your app after authentication. + + To find your app scheme, take a look at `apps/mobile/app.config.ts` file and then add it to your auth server configuration: + + ```ts title="server.ts" + export const auth = betterAuth({ + ... + + trustedOrigins: ["turbostarter://**"], + + ... + }); + ``` + + Adding your app scheme to the trusted origins list is crucial for security - it prevents CSRF attacks and blocks malicious open redirects by ensuring only requests from approved origins (your app) are allowed through. + + [Read more about auth security in Better Auth's documentation.](https://www.better-auth.com/docs/reference/security) + + +## UI + +We have separate configuration that determines what is displayed to your users in the **UI**. It's set at `apps/mobile/config/auth.ts`. + +The recommendation is to **not update this directly** - instead, please define the environment variables and override the default behavior. + +```ts title="apps/mobile/config/auth.ts" +import env from "env.config"; +import { Platform } from "react-native"; + +import { SocialProvider, authConfigSchema } from "@turbostarter/auth"; + +import type { AuthConfig } from "@turbostarter/auth"; + +export const authConfig = authConfigSchema.parse({ + providers: { + password: env.EXPO_PUBLIC_AUTH_PASSWORD, + magicLink: env.EXPO_PUBLIC_AUTH_MAGIC_LINK, + anonymous: env.EXPO_PUBLIC_AUTH_ANONYMOUS, + oAuth: [ + Platform.select({ + android: SocialProvider.GOOGLE, + ios: SocialProvider.APPLE, + }), + SocialProvider.GITHUB, + ], + }, +}) satisfies AuthConfig; +``` + +The configuration is also validated using the Zod schema, so if something is off, you'll see the errors. + +For example, if you want to switch from password to magic link, you'd change the following environment variables: + +```dotenv title=".env.local" +EXPO_PUBLIC_AUTH_PASSWORD=false +EXPO_PUBLIC_AUTH_MAGIC_LINK=true +``` + +To display third-party providers in the UI, you need to set the `oAuth` array to include the provider you want to display. The default is Google and Github. + +```tsx title="apps/web/config/auth.ts" +providers: { + ... + oAuth: [ + Platform.select({ + android: SocialProvider.GOOGLE, + ios: SocialProvider.APPLE, + }), + SocialProvider.GITHUB, + ], + ... +}, +``` + +You can even display specific providers for specific platforms - for example, you can display Google authentication for Android and Apple authentication for iOS. + +## Third party providers + +To enable third-party authentication providers, you'll need to: + +1. Set up an OAuth application in the provider's developer console (like [Apple Developer Portal](https://developer.apple.com/account/), [Google Cloud Console](https://console.cloud.google.com/), [Github Developer Settings](https://github.com/settings/developers) or any other provider you want to use) +2. Configure the corresponding environment variables in your TurboStarter **API (web) application** + +Each OAuth provider requires its own set of credentials and environment variables. Please refer to the [Better Auth documentation](https://better-auth.com/docs/concepts/oauth) for detailed setup instructions for each supported provider. + + + Make sure to set both development and production environment variables + appropriately. Your OAuth provider may require different callback URLs for + each environment. + + + +--- +url: /docs/mobile/auth/flow +title: User flow +description: Discover the authentication flow in Turbostarter. +--- + +TurboStarter ships with a fully functional authentication system. Most of the screens and components are preconfigured and easily customizable to your needs. + +Here you will find a quick walkthrough of the authentication flow. + +## Sign up + +The sign-up screen is where users can create an account. They need to provide their email address and password. + +![Sign up](/images/docs/mobile/auth/sign-up.png) + +Once successful, users are asked to confirm their email address. This is enabled by default - and due to security reasons, it's not possible to disable it. + + + Make sure to configure the [email provider](/docs/web/emails/configuration) together with the [auth hooks](/docs/web/emails/sending#authentication-emails) to be able to send emails from your app. + + +![Confirm email](/images/docs/mobile/auth/confirm-email.png) + +## Sign in + +The sign-in screen is where users can log in to their account. They need to provide their email address and password, use magic link (if enabled) or third-party providers. + +![Sign in](/images/docs/mobile/auth/sign-in.png) + +## Sign out + +The sign out button is located in the user account settings. + +![Settings](/images/docs/mobile/auth/settings.png) + +## Forgot password + +The forgot password screen is where users can reset their password. They need to provide their email address and follow the instructions in the email. + +It comes together with the reset password screen, where users land from a forgot email. There they can reset their password by providing new password and confirming it. + +![Forgot password](/images/docs/mobile/auth/forgot-password.png) + +## Two-factor authentication + +Two-factor authentication is a security feature that requires users to provide a code sent to their email or phone number in addition to their password when logging in. + +![Two-factor authentication](/images/docs/mobile/auth/two-factor/sign-in-prompt.png) + + +--- +url: /docs/mobile/auth/oauth/apple +title: Apple +description: Configure "Sign in with Apple" for your mobile application. +--- + +**"Sign in with Apple"** provides a native, privacy-preserving SSO experience on iOS. Use the system Apple button and the Apple Authentication APIs to sign users in, then verify the identity token on your backend and create a session with your auth server. + + + Native Apple ID authentication is available on iOS only. You are advised to + present the official system button (or our custom component - also compliant!) + and follow [Apple's Human Interface + Guidelines](https://developer.apple.com/design/human-interface-guidelines/sign-in-with-apple) + for best practices. + + +![Sign in with Apple](/images/docs/mobile/auth/sign-in-with-apple.png) + +## Why use native Apple ID authentication? + + + + System sheet + official button, aligned with [Apple's Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/sign-in-with-apple) for trust and conversion. + + + + Private relay email and limited data by design, ensuring your users' privacy is protected and compliant with App Store guidelines. + + + + Fast, low-friction sign-in on iOS enabling your users to sign in without the need to remember or create additional passwords. + + + + JWT verification on the server with [Better Auth](https://www.better-auth.com/docs/authentication/apple), keeping your users' credentials secure. + + + + We exchange Apple credentials for an app session and persist it in the app. + + + +## Requirements + +* Enable the "Sign in with Apple" capability for your bundle identifier in the [Apple Developer Portal](https://developer.apple.com/account/resources/identifiers/list) +* Add the entitlement and build with [EAS](/docs/mobile/publishing/checklist) (or configure natively) +* Ensure your app's deep link scheme is added to the auth server's [trusted origins configuration](/docs/mobile/auth/configuration) + +Check the [Better Auth documentation](https://www.better-auth.com/docs/authentication/apple) for more details on how to configure all the required keys and certificates. + +## High-level flow + +1. Check availability with `AppleAuthentication.isAvailableAsync()`. +2. Render the system `AppleAuthenticationButton` or custom TurboStarter component. +3. Call `AppleAuthentication.signInAsync()` requesting `FULL_NAME` and/or `EMAIL` as needed. +4. Send the returned `idTokeb` identifier to the API powered by [Better Auth](https://www.better-auth.com/docs/authentication/apple) to verify and establish a session. +5. Optionally track credential state with `AppleAuthentication.getCredentialStateAsync(user)`. + + + Always verify the JWT signature from `idToken` on your backend using Apple's + public keys before creating a session. + + +For a more in-depth overview of Apple ID authentication—including implementation details, platform caveats, and advanced configuration—see the following resources: + + + + + + + + + + +--- +url: /docs/mobile/auth/oauth/google +title: Google +description: Configure "Sign in with Google" for your mobile application. +--- + +**"Sign in with Google"** enables a fast account-chooser experience on mobile (especially on Android). Configure your platform credentials, prompt the native account picker, then exchange the returned token on your backend to create a session with your auth server. + + + On Android, Google Sign‑In uses [Google Identity + Services](https://developers.google.com/identity?hl=pl) and integrates with + the system account chooser. On iOS, the recommended Expo flow uses + [expo-auth-session](https://docs.expo.dev/versions/latest/sdk/auth-session/) + with Google for a native, web-based sign-in experience. + + +![Sign in with Google](/images/docs/mobile/auth/sign-in-with-google.png) + +## Why use Google authentication? + + + + Account picker and token storage integrated with the OS for speed and familiarity. + + + + Android native chooser; iOS polished experience via Expo. + + + + Tokens are verified server-side with [Better Auth](https://www.better-auth.com/docs/authentication/google) before a session is issued. + + + + Reduce friction with one-tap sign-in and fewer passwords to remember. + + + + Built on [Google Identity Services](https://developers.google.com/identity?hl=pl) and best-practice OAuth flows. + + + +## Requirements + +* Configure [Google Cloud OAuth Client IDs](https://react-native-google-signin.github.io/docs/setting-up/get-config-file) (Android package + SHA-1, iOS bundle ID) in the [Google Cloud Console](https://console.cloud.google.com/) +* Build with [EAS](/docs/mobile/publishing/checklist) to ensure native credentials are embedded correctly +* Add your app deep link scheme to the auth server's [trusted origins configuration](/docs/mobile/auth/configuration) + +Check the [Better Auth documentation](https://www.better-auth.com/docs/authentication/google) and [`@react-native-google-signin/google-signin` documentation](https://react-native-google-signin.github.io) for steps to configure your server verification, client IDs and more. + +## High-level flow + +1. Configure Google OAuth Client IDs for Android and iOS in [Google Cloud Console](https://console.cloud.google.com/). +2. Initialize the Google auth request in your app and render a "Sign in with Google" button. +3. Prompt the account chooser; on success you receive an `idToken` and/or `accessToken`. +4. Send the tokens to the API powered by [Better Auth](https://www.better-auth.com/docs/authentication/google) to verify and establish a session. +5. Persist the session and proceed to the app. + +For a more in-depth overview of Google authentication, including implementation details, platform caveats, and advanced configuration, see the following resources: + + + + + + + + + + + + +--- +url: /docs/mobile/auth/oauth +title: OAuth +description: Get started with social authentication. +--- + +Better Auth supports almost **30** (!) different [OAuth providers](https://www.better-auth.com/docs/concepts/oauth). They can be easily configured and enabled in the kit without any additional configuration needed. + + + TurboStarter provides you with all the configuration required to handle OAuth providers responses from your app: + + * redirects + * middleware + * confirmation API routes + + You just need to configure one of the below providers on their side and set correct credentials as environment variables in your TurboStarter app. + + +![OAuth providers](/images/docs/web/auth/social-providers.png) + +Third Party providers need to be configured, managed and enabled fully on the provider's side. TurboStarter just needs the correct credentials to be set as environment variables in your app and passed to the [authentication API configuration](/docs/web/auth/configuration#api). + +To enable OAuth providers in your TurboStarter app, you need to: + +1. Set up an OAuth application in the provider's developer console (like [Apple Developer Portal](https://developer.apple.com/account/), [Google Cloud Console](https://console.cloud.google.com/), [Github Developer Settings](https://github.com/settings/developers) or any other provider you want to use) +2. Configure the provider's credentials as environment variables in your app. For example, for Google OAuth: + +```dotenv title="apps/web/.env.local" +GOOGLE_CLIENT_ID= +GOOGLE_CLIENT_SECRET= +``` + +Then, pass it to the authentication configuration in `packages/auth/src/server.ts`: + +```ts title="server.ts" +export const auth = betterAuth({ + ... + + socialProviders: { + [SocialProvider.GOOGLE]: { + clientId: env.GOOGLE_CLIENT_ID, + clientSecret: env.GOOGLE_CLIENT_SECRET, + }, + }, + + ... +}); +``` + + + For mobile apps, we need to define a trusted origin using an app scheme instead of a classic URL. App schemes (like `turbostarter://`) are used for [deep linking](https://docs.expo.dev/guides/linking/) users to specific screens in your app after authentication. + + To find your app scheme, take a look at `apps/mobile/app.config.ts` file and then add it to your auth server configuration: + + ```ts title="server.ts" + export const auth = betterAuth({ + ... + + trustedOrigins: ["turbostarter://**"], + + ... + }); + ``` + + Adding your app scheme to the trusted origins list is crucial for security - it prevents CSRF attacks and blocks malicious open redirects by ensuring only requests from approved origins (your app) are allowed through. + + [Read more about auth security in Better Auth's documentation.](https://www.better-auth.com/docs/reference/security) + + +Also, we included some native integrations (["Sign in with Apple"](/docs/mobile/auth/oauth/apple) for iOS and ["Sign in with Google"](/docs/mobile/auth/oauth/google) for Android) to make the sign-in process smoother and faster for the user. + + +--- +url: /docs/mobile/auth/overview +title: Overview +description: Get started with authentication. +--- + +TurboStarter uses [Better Auth](https://better-auth.com) to handle authentication. It's a secure, production-ready authentication solution that integrates seamlessly with many frameworks and provides enterprise-grade security out of the box. + + + One of the core principles of TurboStarter is to do things **as simple as possible**, and to make everything **as performant as possible**. + + Better Auth provides an excellent developer experience with minimal configuration required, while maintaining enterprise-grade security standards. Its framework-agnostic approach and focus on performance makes it the perfect choice for TurboStarter. + + Recently, Better Auth [announced](https://www.better-auth.com/blog/authjs-joins-better-auth) an incorporation of [Auth.js (27k+ stars on Github)](https://authjs.dev/), making it even more powerful and flexible. + + +![Better Auth](/images/docs/better-auth.png) + +You can read more about Better Auth in the [official documentation](https://better-auth.com/docs). + +TurboStarter supports multiple authentication methods: + +* **Password** - the traditional email/password method +* **Magic Link** - magic links with [deep linking](https://docs.expo.dev/linking/overview) +* **Anonymous** - allowing users to proceed anonymously +* **OAuth** - OAuth social providers ([Apple](https://www.better-auth.com/docs/authentication/apple), [Google](https://www.better-auth.com/docs/authentication/google) and [Github](https://www.better-auth.com/docs/authentication/github) preconfigured) +* **Native Apple authentication** - ["Sign in with Apple"](/docs/mobile/auth/oauth/apple) for iOS +* **Native Google authentication** - ["Sign in with Google"](/docs/mobile/auth/oauth/google) for Android + +As well as common applications flows, with ready-to-use views and components: + +* **Sign in** - sign in with email/password or OAuth providers +* **Sign up** - sign up with email/password or OAuth providers +* **Sign out** - sign out +* **Password recovery** - forgot and reset password +* **Email verification** - verify email + +You can construct your auth flow like LEGO bricks - plug in needed parts and customize them to your needs. + + +--- +url: /docs/mobile/billing +title: Billing +description: Get started with billing in TurboStarter. +--- + + + For now, billing has a limited functionalities on mobile, we're mostly relying on the [web app](/docs/web/billing/overview) to handle billing. + + We are working on a fully-featured mobile billing to help you monetize your mobile app easier. Stay tuned for updates. + + [See roadmap](https://github.com/orgs/turbostarter/projects/1) + + +## Fetching customer data + +When your user purchased a plan from your landing page or web app, you can easily fetch their data using the [API](/docs/mobile/api/client). + +To do so, just call the `/api/billing/customer` endpoint: + +```tsx title="customer-screen.tsx" +import { api } from "~/lib/api"; + +export default function CustomerScreen() { + const { data: customer, isLoading } = useQuery({ + queryKey: ["customer"], + queryFn: handle(api.billing.customer.$get), + }); + + if (isLoading) return Loading...; + + return {customer?.plan}; +} +``` + +You may also want to ensure that user is logged in before fetching their billing data to avoid unnecessary API calls. + +```tsx title="customer-screen.tsx" +import { api } from "~/lib/api"; +import { authClient } from "~/lib/auth"; + +export default function CustomerScreen() { + const { + data: { user }, + } = authClient.useSession(); + + const { data: customer } = useQuery({ + queryKey: ["customer"], + queryFn: handle(api.billing.customer.$get), + enabled: !!user, // [!code highlight] + }); + + if (!user || !customer) { + return null; + } + + return ( + + {user.email} + {customer.plan} + + ); +} +``` + + + Be mindful when implementing payment-related features in your mobile app. Apple has strict guidelines regarding external payment systems and **may reject your app** if you aggressively redirect users to web-based payment flows. Make sure to review the [App Store Review Guidelines](https://developer.apple.com/app-store/review/guidelines/#payments) carefully and consider implementing native in-app purchases for iOS users to ensure compliance. + + We are currently working on a fully native payments system that will make it easier to comply with Apple's guidelines - stay tuned for updates! + + + +--- +url: /docs/mobile/cli +title: CLI +description: Start your new project with a single command. +--- + + + +To help you get started with TurboStarter **as quickly as possible**, we've developed a [CLI](https://www.npmjs.com/package/@turbostarter/cli) that enables you to create a new project (with all the configuration) in seconds. + +The CLI is a set of commands that will help you create a new project, generate code, and manage your project efficiently. + +Currently, the following action is available: + +* **Starting a new project** - Generate starter code for your project with all necessary configurations in place (billing, database, emails, etc.) + +**The CLI is in beta**, and we're actively working on adding more commands and actions. Soon, the following features will be available: + +* **Translations** - Translate your project, verify translations, and manage them effectively +* **Installing plugins** - Easily install plugins for your project +* **Dynamic code generation** - Generate dynamic code based on your project structure + +## Installation + +You can run commands using `npx`: + +```bash +npx turbostarter +npx @turbostarter/cli@latest +``` + + + If you don't want to install the CLI globally, you can simply replace the examples below with `npx @turbostarter/cli@latest` instead of `turbostarter`. + + This also allows you to always run the latest version of the CLI without having to update it. + + +## Usage + +Running the CLI without any arguments will display the general information about the CLI: + +```bash +Usage: turbostarter [options] [command] + +Your Turbo Assistant for starting new projects, adding plugins and more. + +Options: + -v, --version display the version number + -h, --help display help for command + +Commands: + new create a new TurboStarter project + help [command] display help for command +``` + +You can also display help for it or check the actual version. + +### Starting a new project + +Use the `new` command to initialize configuration and dependencies for a new project. + +```bash +npx turbostarter new +``` + +You will be asked a few questions to configure your project: + +```bash +✔ All prerequisites are satisfied, let's start! 🚀 + +? What do you want to ship? › + ◉ Web app + ◉ Mobile app + ◯ Browser extension +? Enter your project name. › +? How do you want to use database? › + Local (powered by Docker) + Cloud +? What do you want to use for billing? › + Stripe + Lemon Squeezy + +... + +🎉 You can now get started. Open the project and just ship it! 🎉 + +Problems? https://www.turbostarter.dev/docs +``` + +It will create a new project, configure providers, install dependencies and start required services in development mode. + + +--- +url: /docs/mobile/configuration/app +title: App configuration +description: Learn how to setup the overall settings of your app. +--- + +When configuring your app, you'll need to define settings in different places depending on which provider will use them (e.g., Expo, EAS). + +## App configuration + +Let's start with the core settings for your app. These settings are **crucial** as they're used by Expo and EAS to build your app, determine its store presence, prepare updates, and more. + +This configuration includes essential details like the official name, description, scheme, store IDs, splash screen configuration, and more. + +You'll define these settings in `apps/mobile/app.config.ts`. Make sure to follow the [Expo config schema](https://docs.expo.dev/versions/latest/config/app/) when setting this up. + +Here is an example of what the config file looks like: + +```ts title="apps/mobile/app.config.ts" +import { ExpoConfig } from "expo/config"; + +export default ({ config }: ConfigContext): ExpoConfig => ({ + ...config, + name: "TurboStarter", + slug: "turbostarter", + scheme: "turbostarter", + version: "0.1.0", + orientation: "portrait", + icon: "./assets/images/icon.png", + userInterfaceStyle: "automatic", + assetBundlePatterns: ["**/*"], + sdkVersion: "51.0.0", + platforms: ["ios", "android"], + updates: { + fallbackToCacheTimeout: 0, + }, + newArchEnabled: true, + ios: { + bundleIdentifier: "your.bundle.identifier", + supportsTablet: false, + }, + android: { + package: "your.bundle.identifier", + adaptiveIcon: { + monochromeImage: "./public/images/icon/android/monochrome.png", + foregroundImage: "./public/images/icon/android/adaptive.png", + backgroundColor: "#0D121C", + }, + }, + extra: { + eas: { + projectId: "your-project-id", + }, + }, + experiments: { + tsconfigPaths: true, + typedRoutes: true, + }, + plugins: ["expo-router", ["expo-splash-screen", SPLASH]], +}); +``` + +Make sure to replace the values with your own and take your time to set everything correctly. + + + +### Internal configuration + +The same as for the [web app](/docs/web/configuration/app), and [extension](/docs/extension/configuration/app), we're defining the internal app config, which stores some overall variables for your application (that can't be read from Expo config). + +The recommendation is to **not update this directly** - instead, please define the environment variables and override the default behavior. The configuration is strongly typed so you can use it safely accross your codebase - it'll be validated at build time. + +```ts title="apps/mobile/src/config/app.ts" +import env from "env.config"; + +export const appConfig = { + locale: env.EXPO_PUBLIC_DEFAULT_LOCALE, + url: env.EXPO_PUBLIC_SITE_URL, + theme: { + mode: env.EXPO_PUBLIC_THEME_MODE, + color: env.EXPO_PUBLIC_THEME_COLOR, + }, +} as const; +``` + +For example, to set the mobile app default theme color, you'd update the following variable: + +```dotenv title=".env.local" +EXPO_PUBLIC_THEME_COLOR="yellow" +``` + + + Do NOT use `process.env` to get the values of the variables. Variables + accessed this way are not validated at build time, and thus the wrong variable + can be used in production. + + +## EAS configuration + +To properly build and publish your app, you need to define settings for the EAS build service. + +This is done in `apps/mobile/eas.json` and it must follow the [EAS config scheme](https://docs.expo.dev/eas/json/). + +Here is an example of what the config file looks like: + +```json title="apps/mobile/eas.json" +{ + "cli": { + "version": ">= 4.1.2" + }, + "build": { + "base": { + "node": "20.15.0", + "pnpm": "9.6.0", + "ios": { + "resourceClass": "m-medium" + }, + "env": { + "EXPO_PUBLIC_DEFAULT_LOCALE": "en", + "EXPO_PUBLIC_AUTH_PASSWORD": "true", + "EXPO_PUBLIC_AUTH_MAGIC_LINK": "false", + "EXPO_PUBLIC_THEME_MODE": "system", + "EXPO_PUBLIC_THEME_COLOR": "orange" + } + }, + ... + "preview": { + "extends": "base", + "distribution": "internal", + "android": { + "buildType": "apk" + }, + "env": { + "APP_ENV": "test", + } + }, + "production": { + "extends": "base", + "env": { + "APP_ENV": "production", + } + } + ... + }, +} +``` + +Make sure to also fill all the [environment variables](/docs/mobile/configuration/environment-variables) with the correct values for your project and correct environment, otherwise your app won't build and you won't be able to publish it. + + + + +--- +url: /docs/mobile/configuration/environment-variables +title: Environment variables +description: Learn how to configure environment variables. +--- + +Environment variables are defined in the `.env` file in the root of the repository and in the root of the `apps/mobile` package. + +* **Shared environment variables**: Defined in the **root** `.env` file. These are shared between environments (e.g., development, staging, production) and apps (e.g., web, mobile). +* **Environment-specific variables**: Defined in `.env.development` and `.env.production` files. These are specific to the development and production environments. +* **App-specific variables**: Defined in the app-specific directory (e.g., `apps/web`). These are specific to the app and are not shared between apps. +* **Build environment variables**: Not stored in the `.env` file. Instead, they are stored in `eas.json` file used to build app on [Expo Application Services](https://expo.dev/eas). +* **Secret keys**: They're not stored on mobile side, instead [they're defined on the web side.](/docs/web/configuration/environment-variables#secret-keys) + +## Shared variables + +Here you can add all the environment variables that are shared across all the apps. + +To override these variables in a specific environment, please add them to the specific environment file (e.g. `.env.development`, `.env.production`). + +```dotenv title=".env.local" +# Shared environment variables + +# The database URL is used to connect to your database. +DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres" + +# The name of the product. This is used in various places across the apps. +PRODUCT_NAME="TurboStarter" + +# The url of the web app. Used mostly to link between apps. +URL="http://localhost:3000" + +... +``` + +## App-specific variables + +Here you can add all the environment variables that are specific to the app (e.g. `apps/mobile`). + +You can also override the shared variables defined in the root `.env` file. + +```dotenv title="apps/mobile/.env.local" +# App-specific environment variables + +# Env variables extracted from shared to be exposed to the client in Expo app +EXPO_PUBLIC_SITE_URL="${URL}" +EXPO_PUBLIC_DEFAULT_LOCALE="${DEFAULT_LOCALE}" + +# Theme mode and color +EXPO_PUBLIC_THEME_MODE="system" +EXPO_PUBLIC_THEME_COLOR="orange" + +# Use this variable to enable or disable password-based authentication. If you set this to true, users will be able to sign up and sign in using their email and password. If you set this to false, the form won't be shown. +EXPO_PUBLIC_AUTH_PASSWORD="true" + +... +``` + + + To make environment variables available in the Expo app code, you need to prefix them with `EXPO_PUBLIC_`. They will be injected to the code during the build process. + + Only environment variables prefixed with `EXPO_PUBLIC_` will be injected. + + [Read more about Expo environment variables.](https://docs.expo.dev/guides/environment-variables/) + + +## Build environment variables + +To allow your app to build properly on [EAS](https://expo.dev/eas) you need to define your environment variables either in your `eas.json` file under corresponding profile (e.g. `preview` or `production`) or directly in the [EAS platform](https://docs.expo.dev/eas/environment-variables/): + +![EAS environment variables](/images/docs/mobile/eas-environment-variables.png) + +Then, when you trigger build, correct environment variables will be injected to your mobile app code ensuring that everything is working correctly. + +[Check EAS documentation for more details.](https://docs.expo.dev/eas/environment-variables/) + +## Secret keys + +Secret keys and sensitive information are to be **never** stored on the mobile app code. + + + It means that you will need to add the secret keys to the **web app, where the API is deployed.** + + The mobile app should only communicate with the backend API, which is typically part of the web app. The web app is responsible for handling sensitive operations and storing secret keys securely. + + [See web documentation for more details.](/docs/web/configuration/environment-variables#secret-keys) + + This is not a TurboStarter-specific requirement, but a best practice for security for any + application. Ultimately, it's your choice. + + + +--- +url: /docs/mobile/configuration/paths +title: Paths configuration +description: Learn how to configure the paths of your app. +--- + +The paths configuration is set at `apps/mobile/config/paths.ts`. This configuration stores all the paths that you'll be using in your application. It is a convenient way to store them in a central place rather than scatter them in the codebase using magic strings. + +It is **unlikely you'll need to change** this unless you're heavily editing the codebase. + +```ts title="apps/mobile/config/paths.ts" +const pathsConfig = { + index: "/", + setup: { + welcome: "/welcome", + auth: { + login: `${AUTH_PREFIX}/login`, + register: `${AUTH_PREFIX}/register`, + forgotPassword: `${AUTH_PREFIX}/password/forgot`, + updatePassword: `${AUTH_PREFIX}/password/update`, + error: `${AUTH_PREFIX}/error`, + join: `${AUTH_PREFIX}/join`, + }, + steps: { + start: `${STEPS_PREFIX}/start`, + required: `${STEPS_PREFIX}/required`, + skip: `${STEPS_PREFIX}/skip`, + final: `${STEPS_PREFIX}/final`, + }, + }, + dashboard: { + user: { + index: DASHBOARD_PREFIX, + ai: `${DASHBOARD_PREFIX}/ai`, + ... + } + ... + } +} as const; +``` + + + By declaring the paths as constants, we can use them safely throughout the + codebase. There is no risk of misspelling or using magic strings. + + + +--- +url: /docs/mobile/customization/add-app +title: Adding apps +description: Learn how to add apps to your Turborepo workspace. +--- + + + This is an **advanced topic** - you should only follow these instructions if you are sure you want to add a new app to your TurboStarter project within your monorepo and want to keep pulling updates from the TurboStarter repository. + + +In some ways - creating a new repository may be the easiest way to manage your application. However, if you want to keep your application within the monorepo and pull updates from the TurboStarter repository, you can follow these instructions. + +To pull updates into a separate application outside of `mobile` - we can use [git subtree](https://www.atlassian.com/git/tutorials/git-subtree). + +Basically, we will create a subtree at `apps/mobile` and create a new remote branch for the subtree. When we create a new application, we will pull the subtree into the new application. This allows us to keep it in sync with the `apps/mobile` folder. + +To add a new app to your TurboStarter project, you need to follow these steps: + + + + ## Create a subtree + + First, we need to create a subtree for the `apps/mobile` folder. We will create a branch named `mobile-branch` and create a subtree for the `apps/mobile` folder. + + ```bash + git subtree split --prefix=apps/mobile --branch mobile-branch + ``` + + + + ## Create a new app + + Now, we can create a new application in the `apps` folder. + + Let's say we want to create a new app `ai-chat` at `apps/ai-chat` with the same structure as the `apps/mobile` folder (which acts as the template for all new apps). + + ```bash + git subtree add --prefix=apps/ai-chat origin mobile-branch --squash + ``` + + You should now be able to see the `apps/ai-chat` folder with the contents of the `apps/mobile` folder. + + + + ## Update the app + + When you want to update the new application, follow these steps: + + ### Pull the latest updates from the TurboStarter repository + + The command below will update all the changes from the TurboStarter repository: + + ```bash + git pull upstream main + ``` + + ### Push the `mobile-branch` updates + + After you have pulled the updates from the TurboStarter repository, you can split the branch again and push the updates to the mobile-branch: + + ```bash + git subtree split --prefix=apps/mobile --branch mobile-branch + ``` + + Now, you can push the updates to the `mobile-branch`: + + ```bash + git push origin mobile-branch + ``` + + ### Pull the updates to the new application + + Now, you can pull the updates to the new application: + + ```bash + git subtree pull --prefix=apps/ai-chat origin mobile-branch --squash + ``` + + + +That's it! You now have a new application in the monorepo 🎉 + + +--- +url: /docs/mobile/customization/add-package +title: Adding packages +description: Learn how to add packages to your Turborepo workspace. +--- + + + This is an **advanced topic** - you should only follow these instructions if you are sure you want to add a new package to your TurboStarter application instead of adding a folder to your application in `apps/mobile` or modify existing packages under `packages`. You don't need to do this to add a new screen or component to your application. + + +To add a new package to your TurboStarter application, you need to follow these steps: + + + + ## Generate a new package + + First, enter the command below to create a new package in your TurboStarter application: + + ```bash + turbo gen package + ``` + + Turborepo will ask you to enter the name of the package you want to create. Enter the name of the package you want to create and press enter. + + If you don't want to add dependencies to your package, you can skip this step by pressing enter. + + The command will have generated a new package under packages named `@turbostarter/`. If you named it `example`, the package will be named `@turbostarter/example`. + + + + ## Export a module from your package + + By default, the package exports a single module using the `index.ts` file. You can add more exports by creating new files in the package directory and exporting them from the `index.ts` file or creating export files in the package directory and adding them to the `exports` field in the `package.json` file. + + ### From `index.ts` file + + The easiest way to export a module from a package is to create a new file in the package directory and export it from the `index.ts` file. + + ```ts title="packages/example/src/module.ts" + export function example() { + return "example"; + } + ``` + + Then, export the module from the `index.ts` file. + + ```ts title="packages/example/src/index.ts" + export * from "./module"; + ``` + + ### From `exports` field in `package.json` + + **This can be very useful for tree-shaking.** Assuming you have a file named `module.ts` in the package directory, you can export it by adding it to the `exports` field in the `package.json` file. + + ```json title="packages/example/package.json" + { + "exports": { + ".": "./src/index.ts", + "./module": "./src/module.ts" + } + } + ``` + + **When to do this?** + + 1. when exporting two modules that don't share dependencies to ensure better tree-shaking. For example, if your exports contains both client and server modules. + 2. for better organization of your package + + For example, create two exports `client` and `server` in the package directory and add them to the `exports` field in the `package.json` file. + + ```json title="packages/example/package.json" + { + "exports": { + ".": "./src/index.ts", + "./client": "./src/client.ts", + "./server": "./src/server.ts" + } + } + ``` + + 1. The `client` module can be imported using `import { client } from '@turbostarter/example/client'` + 2. The `server` module can be imported using `import { server } from '@turbostarter/example/server'` + + + + ## Use the package in your application + + You can now use the package in your application by importing it using the package name: + + ```ts title="apps/mobile/src/app/index.tsx" + import { example } from "@turbostarter/example"; + + console.log(example()); + ``` + + + +Et voilà! You have successfully added a new package to your TurboStarter application. 🎉 + + +--- +url: /docs/mobile/customization/components +title: Components +description: Manage and customize your app components. +--- + +For the components part, we're using [react-native-reusables](https://reactnativereusables.com//getting-started/introduction/) for atomic, accessible and highly customizable components. + +> It's like shadcn/ui, but for mobile apps. + + + react-native-reusables is a powerful tool that allows you to generate + pre-designed components with a single command. It's built with Uniwind (like + Tailwind CSS for mobile) and accessibility in mind, it's also highly + customizable. + + +TurboStarter defines two packages that are responsible for the UI part of your app: + +* `@turbostarter/ui` - shared styles, [themes](/docs/mobile/customization/styling#themes) and assets (e.g. icons) +* `@turbostarter/ui-mobile` - pre-built UI mobile components, ready to use in your app + +## Adding a new component + +There are basically two ways to add a new component: + + + + TurboStarter is fully compatible with [react-native-reusables CLI](https://www.npmjs.com/package/@react-native-reusables/cli), so you can generate new components with single command. + + Run the following command from the **root** of your project: + + ```bash + pnpm --filter @turbostarter/ui-mobile ui:add + ``` + + This will launch an interactive command-line interface to guide you through the process of adding a new component where you can pick which component you want to add. + + ```bash + Which components would you like to add? > Space to select. A to toggle all. + Enter to submit. + + ◯ accordion + ◯ alert + ◯ alert-dialog + ◯ aspect-ratio + ◯ avatar + ◯ badge + ◯ button + ◯ calendar + ◯ card + ◯ checkbox + ``` + + Newly created components will appear in the `packages/ui/mobile/src` directory. + + + + You can always copy-paste a component from the [react-native-reusables](https://reactnativereusables.com//getting-started/introduction/) website and modify it to your needs. + + This is possible, because the components are headless and don't need (in most cases) any additional dependencies. + + Copy code from the website, create a new file in the `packages/ui/mobile/src` directory and paste the code into the file. + + + + + Keep in mind that you should always try to keep shared components as atomic as possible. This will make it easier to reuse them and to build specific views by composition. + + E.g. include components like `Button`, `Input`, `Card`, `Dialog` in shared package, but keep specific components like `LoginForm` in your app directory. + + +## Using components + +Each component is a standalone entity which has a separate export from the package. It helps to keep things modular, avoid unnecessary dependencies and make tree-shaking possible. + +To import a component from the UI package, use the following syntax: + +```tsx title="apps/mobile/src/modules/common/my-component.tsx" +// [!code word:card] +import { + Card, + CardContent, + CardHeader, + CardFooter, + CardTitle, + CardDescription, +} from "@turbostarter/ui-mobile/card"; +``` + +Then you can use it to build a component specific to your app: + +```tsx title="apps/mobile/src/modules/common/my-component.tsx" +export function MyComponent() { + return ( + + + My Component + + + My Component Content + + + + + + ); +} +``` + + + Most of the components are the same as for the [web app](/docs/web/customization/components). + + It means that you can basically migrate existing web components to the mobile app with just an import change! + + + + + +--- +url: /docs/mobile/customization/styling +title: Styling +description: Get started with styling your app. +--- + +To build the mobile user interface, TurboStarter comes with [Uniwind](https://uniwind.dev/) pre-configured. + + + Uniwind brings Tailwind CSS utilities to React Native. It lets you style with familiar classes while keeping native performance and platform-appropriate primitives. + + +## Tailwind configuration + +In the `packages/ui/shared/src/styles` directory, you will find shared CSS files with Tailwind configuration. To change global styles, edit the files in this folder. + +Here is an example of a shared CSS file that includes the Tailwind CSS configuration: + +```css title="packages/ui/shared/src/styles/globals.css" +@import "tailwindcss"; +@import "./themes.css"; + +@custom-variant dark (&:is(.dark *)); + +:root { + --radius: 0.65rem; +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + ... +} +``` + +For colors, we rely strictly on [CSS Variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties) in [OKLCH](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch) format to allow for easy theme management without the need for any JavaScript. + +Also, each app has its own `globals.css` file, which extends the shared config and allows you to override global styles. + +Here is an example of an app's `globals.css` file: + +```css title="apps/mobile/src/assets/styles/globals.css" +@import "@turbostarter/ui/globals.css"; +@import "uniwind"; + +@theme inline { + --font-sans: "Geist_400Regular"; + --font-sans-medium: "Geist_500Medium"; + --font-sans-semibold: "Geist_600SemiBold"; + --font-sans-bold: "Geist_700Bold"; + --font-mono: "GeistMono_400Regular"; +} +``` + +This keeps a clear separation of concerns and a consistent structure for the Tailwind CSS configuration across apps. + +## Themes + +TurboStarter comes with **9+** predefined themes, which you can use to quickly change the look and feel of your app. + +They're defined in the `packages/ui/shared/src/styles/themes` directory. Each theme is a set of variables that can be overridden: + +```ts title="packages/ui/shared/src/styles/themes/colors/orange.ts" +export const orange = { + light: { + background: [1, 0, 0], + foreground: [0.141, 0.005, 285.823], + card: [1, 0, 0], + "card-foreground": [0.141, 0.005, 285.823], + ... + } +} satisfies ThemeColors; +``` + +Each variable is stored as a [OKLCH](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch) array, which is then converted to a CSS variable at build time (by our custom build script). That way we can ensure full type-safety and reuse themes across different parts of our apps (e.g. use the same theme in emails). + +These variables are consumed across platforms. On mobile, the theme provider injects the shared variables into the app, so Uniwind utility classes like `bg-background` and `text-foreground` resolve correctly. + +Feel free to add your own themes or override the existing ones to match your brand's identity. + +To apply a custom theme to your app, use a `useTheme` hook to modify the config: + +```tsx title="apps/mobile/src/lib/providers/theme.tsx" +import { ThemeColor, ThemeMode } from "@turbostarter/ui"; + +import { useTheme } from "~/modules/common/hooks/use-theme"; + +export const ThemeSwitcher = () => { + const { setConfig } = useTheme(); + + return ( + + setConfig({ mode: ThemeMode.DARK, color: ThemeColor.BLUE }) + } + > + Change the theme to dark blue + + ); +}; +``` + +Under the hood, the `useTheme` hook uses [Uniwind.setTheme](https://docs.uniwind.dev/theming/basics#switch-to-a-specific-theme) and [updateCSSVariables](https://docs.uniwind.dev/theming/update-css-variables) utilities to apply the correct theme to the app together with its variables. + +## Dark mode + +TurboStarter comes with built-in dark mode support. + +Each theme has a corresponding set of dark mode variables, which are used to switch the theme to its dark mode counterpart. + +```ts title="packages/ui/shared/src/styles/themes/colors/orange.ts" +export const orange = { + light: {}, + dark: { + background: [0.141, 0.005, 285.823], + foreground: [0.985, 0, 0], + card: [0.21, 0.006, 285.885], + "card-foreground": [0.985, 0, 0], + ... + } +} satisfies ThemeColors; +``` + +Our custom implementation reads the system color scheme via `useColorScheme` and applies `dark:` variants automatically. With the provider injecting shared variables, dark mode works out of the box. + +You can also define the default theme mode and color in the [app configuration](/docs/mobile/configuration/app). + + + + + + + + + + +--- +url: /docs/mobile/database +title: Database +description: Get started with the database. +--- + + + To enable communication between your Expo app and the server in a production environment, the web application with Hono API must be deployed first. + + + + + + + + +As a mobile app uses only client-side code, **there's no way to interact with the database directly**. + +Also, you should avoid any workarounds to interact with the database directly, because it can lead to leaking your database credentials and other security issues. + +## Recommended approach + +You can safely use the [API](/docs/mobile/api/overview) and call the endpoints which will run queries on the database. + +To do this you need to set up the database on the [web, server side](/docs/web/database/overview) and then use the [API client](/docs/mobile/api/client) to interact with it. + +Learn more about its configuration in the web part of the docs, especially in the following sections: + + + + + + + + + + + + +--- +url: /docs/mobile/extras +title: Extras +description: See what you get together with the code. +--- + +## Tips and Tricks + +In many places, next to the code you will find some marketing tips, design suggestions, and potential risks. This is to help you build a better product and avoid common pitfalls. + +```tsx title="Hero.tsx" +return ( +
+ {/* 💡 Use something that user can visualize e.g. + "Ship your startup while on the toilet" */} +

Best startup on the world

+
+); +``` + +### Submission tips + +When it comes to mobile app and browser extension, you must submit your product to review from Apple/Google etc. We have some tips for you to make sure your submission goes smoothly. + +```json title="app.json" +{ + "ios": { + "infoPlist": { + /* 🍎 add descriptive justification of using this permission on iOS */ + "NSCameraUsageDescription": "This app uses the camera to scan barcodes on event tickets." + } + } +} +``` + +As well as providing you with the info on how to make your store listings better: + +```json title="package.json" +{ + "manifest": { + /* 💡 Use localized messages to get more visibility in web stores */ + "name": "__MSG_extensionName__", + "default_locale": "en" + } +} +``` + +## Discord community + +We have a Discord community where you can ask questions and share your projects. It's a great place to get help and meet other developers. Check more details at [/discord](/discord). + + + +![Discord](/images/docs/discord.png) + +## 25+ SaaS Ideas + +Not sure what to build? We have a list of **25+** SaaS ideas that you can use to get started 🔥 + +Grouped by category, these ideas are a great way to get inspired and start building your next project. + +Including design, copies, marketing tips and potential risks, this list is a great resource for anyone looking to build a SaaS product. + +![SaaS Ideas](/images/docs/saas-ideas.png) + + +--- +url: /docs/mobile/faq +title: FAQ +description: Find answers to common technical questions. +--- + +## Why isn't everything hidden and configured with one BIG config file? + +TurboStarter intentionally exposes the underlying code rather than hiding it behind configuration files (like some starters do). This design choice follows our **you own your code** philosophy, giving you full control and flexibility over your codebase. + +While a single config file might seem simpler initially, it often becomes restrictive when you need to customize functionality beyond what the config allows. With direct access to the code, you can modify any part of the system to match your specific requirements. + +## I don't know some technology! Should I buy TurboStarter? + +You should be prepared for a learning curve or consider learning it first. However, TurboStarter will still work for you if you're willing to learn. + +Even without knowing some technologies, you can still use the rest of the features. + +## I don't need mobile app or browser extension, what should I do? + +You can simply ignore the mobile app and browser extension parts of the project. You can remove the `apps/mobile` and `apps/extension` directories from the project. + +The modular nature of TurboStarter allows you to remove parts of the project that you don't need without affecting the rest of the stack. + +## I want to use a different provider for X + +Sure! TurboStarter is designed to be modular, so configuring new provider (e.g. for emails, billing or any other service) is straightforward. You just need to make sure your configuration is compatible with common interface to be able to plug it into the codebase. + +## Will you add more packages in the future? + +Yes, we will keep updating TurboStarter with new packages and features. This kit is designed to be modular, allowing for new features and packages to be added without interfering with your existing code. You can always [update your project](/docs/web/installation/update) to the latest version. + +## Can I use this kit for a non-SaaS project? + +This kit is mainly designed for SaaS projects. If you're building something other than a SaaS, the Next.js SaaS Boilerplate might include features you don't need. You can still use it for non-SaaS projects, but you may need to remove or modify features that are specific to SaaS use cases. + +## Can I use personal accounts only? + +Yes! You can disable team accounts and have personal accounts only by setting a feature flag. + +## Does it set up the production instance for me? + +No, TurboStarter does not set up the production instance for you. This includes setting up databases, Stripe, or any other services you need. TurboStarter does not have access to your Stripe or Resend accounts, so setup on your end is required. TurboStarter provides the codebase and documentation to help you set up your SaaS project. + +## Does the starter include Solito? + +No. Solito will not be included in this repo. It is a great tool if you want to share code between your Next.js and Expo app. However, the main purpose of this repo is not the integration between Next.js and Expo — it's the code splitting of your SaaS platforms into a monorepo. You can utilize the monorepo with multiple apps, and it can be any app such as Vite, Electron, etc. + +Integrating Solito into this repo isn't hard, and there are a few [official templates](https://github.com/nandorojo/solito/tree/master/example-monorepos) by the creators of Solito that you can use as a reference. + +## Does this pattern leak backend code to my client applications? + +No, it does not. The `api` package should only be a production dependency in the Next.js application where it's served. The Expo app, browser extension, and all other apps you may add in the future should only add the `api` package as a dev dependency. This lets you have full type safety in your client applications while keeping your backend code safe. + +If you need to share runtime code between the client and server, you can create a separate `shared` package for this and import it on both sides. + +## How do I get support if I encounter issues? + +For support, you can: + +1. Visit our [Discord](https://discord.gg/KjpK2uk3JP) +2. Contact us via support email ([hello@turbostarter.dev](mailto:hello@turbostarter.dev)) + +## Are there any example projects or demos? + +Yes - feel free to check out our demo app at [demo.turbostarter.dev](https://demo.turbostarter.dev). Also, you can get inspired by projects built by our customers - take a look at [Showcase](/#showcase). + +## How do I deploy my application? + +Please check the [production checklist](/docs/web/deployment/checklist) for more information. + +## How do I update my project when a new version of the boilerplate is released? + +Please read the [documentation for updating your TurboStarter code](/docs/web/installation/update). + +## Can I use the React package X with this kit? + +Yes, you can use any React package with this kit. The kit is based on React, so you are generally only constrained by the underlying technologies and not by the kit itself. Since you own and can edit all the code, you can adapt the kit to your needs. However, if there are limitations with the underlying technology, you might need to work around them. + +## Can I integrate TurboStarter into an existing project? + +TurboStarter is a full-stack starter intended to be used as the foundation of your app. You can copy individual modules or patterns into an existing codebase, but retrofitting the entire starter into a mature project is typically not recommended and is not officially supported. If you choose to copy parts, prefer isolating boundaries (e.g., `packages/` modules) and aligning interfaces first. + +## Where can I deploy my application? + +TurboStarter targets modern Node.js/Next.js runtimes. You can deploy to providers that support these environments, such as [Vercel](/docs/web/deployment/vercel), [Railway](/docs/web/deployment/railway), [Render](/docs/web/deployment/render), [Fly](/docs/web/deployment/fly), or [Netlify](/docs/web/deployment/netlify) - following their Next.js guidance. Review our [production checklist](/docs/web/deployment/checklist) before going live. + +## Can I easily swap providers (billing, email, etc.)? + +Yes. The starter organizes integrations behind clear interfaces so you can replace providers (e.g., billing or email) with minimal surface changes. Keep your implementation behind a module boundary and adapt to the existing types to avoid ripple effects. + + +--- +url: /docs/mobile +title: Introduction +description: Get started with TurboStarter mobile kit. +--- + +Welcome to the TurboStarter documentation. This is your starting point for learning about the starter kit, its structure, features, and how to use it for your app development. + + + +## What is TurboStarter? + +TurboStarter is a fullstack starter kit that helps you build scalable and production-ready web apps, mobile apps, and browser extensions in minutes. + +Looking to bootstrap your project quickly? Check out our [TurboStarter CLI guide](/blog/the-only-turbo-cli-you-need-to-start-your-next-project-in-seconds) to get started in seconds. + +## Demo apps + +TurboStarter provides a suite of live demo applications you can try instantly - right in your browser, on your phone, or via browser extensions. Try them live by clicking the buttons below. + + + +## Principles + +TurboStarter is built with the following principles: + +* **As simple as possible** - It should be easy to understand, easy to use, and strongly avoid overengineering things. +* **As few dependencies as possible** - It should have as few dependencies as possible to allow you to take full control over every part of the project. +* **As performant as possible** - It should be fast and light without any unnecessary overhead. + +## Features + +Before diving into the technical details, let's overview the features TurboStarter provides. + +### Multi-platform development + +* [Web](/docs/web/stack): Build web apps with React, Next.js, and Tailwind CSS. +* [Mobile](/docs/mobile/stack): Build mobile apps with React Native and Expo. +* [Browser extension](/docs/extension/stack): Build browser extensions with React and WXT. + +For those interested in AI development, check out our dedicated [TurboStarter AI documentation](/ai/docs) with specialized features for building AI-powered applications. + + + Most features are available on all platforms. You can use the **same codebase** to build web, mobile, and browser extension apps. + + +### Authentication + + + + + + + + + + + + + + + + + + + +### Organizations/teams + + + + + + + + + + + +### Billing + + + + + + + + + + + + + + + +### Database + + + + + + + + + + + +### API + + + + + + + + + + + + + +### Admin + + + + + + + + + + + +### AI + + + + Seamless integration of OpenAI, Anthropic, Groq, Mistral, and Gemini. For more advanced AI features, check out [TurboStarter AI](/ai/docs). + + + + + + + + + +### Internationalization + + + + + + + + + + + +### Emails + + + + + + + + + + + +### Landing page + + + + + + + + + + + + + + + +### Marketing + + + + + + + + + + + + + + + + + +### Storage + + + + + + + +### CMS + + + + + + + +### Theming + + + + + + + + + + + +### Analytics + + + + + + + + + + + +### Monitoring + + + + + + + + + + + +### Deployment + + + + + + + + + + + +### Testing + + + + + + + + + + + +## Use like LEGO blocks + +The biggest advantage of TurboStarter is its modularity. You can use the entire stack or just the parts you need. It's like LEGO blocks - you can build anything you want with it. + +If you don't need a specific feature, feel free to remove it without affecting the rest of the stack. + +This approach allows for: + +* **Easy feature integration** - plug new features into the kit with minimal changes. +* **Simplified maintenance** - keep the codebase clean and maintainable. +* **Core feature separation** - distinguish between core features and custom features. +* **Additional modules** - easily add modules like billing, CMS, monitoring, logger, mailer, and more. + +## Scope of this documentation + +While building a SaaS application involves many moving parts, this documentation focuses specifically on TurboStarter. For in-depth information on the underlying technologies, please refer to their respective official documentation. + +This documentation will guide you through configuring, running, and deploying the kit, and will provide helpful links to the official documentation of technologies where necessary. + +## `llms.txt` + +You can access the entire TurboStarter documentation in Markdown format at [/llms.txt](/llms.txt). This can be used to ask any LLM (assuming it has a big enough context window) questions about the TurboStarter based on the most up-to-date documentation. + +### Example usage + +For instance, to prompt an LLM with questions about the TurboStarter: + +1. Copy the documentation contents from [/llms.txt](/llms.txt) +2. Use the following prompt format: + +``` +Documentation: +{paste documentation here} +--- +Based on the above documentation, answer the following: +{your question} +``` + +## Enjoy! + +This documentation is designed to be easy to follow and understand. If you have any questions or need help, feel free to reach out to us at [hello@turbostarter.dev](mailto:hello@turbostarter.dev). + +Explore new features, build amazing apps, and have fun! 🚀 + + +--- +url: /docs/mobile/installation/clone +title: Cloning repository +description: Get the code to your local machine and start developing your app. +--- + + + Ensure you have Git installed on your local machine before proceeding. You can download Git from [here](https://git-scm.com). + + +## Git clone + +Clone the repository using the following command: + +```bash +git clone git@github.com:turbostarter/core +``` + +By default, we're using [SSH](https://docs.github.com/en/authentication/connecting-to-github-with-ssh) for all Git commands. If you don't have it configured, please refer to the [official documentation](https://docs.github.com/en/authentication/connecting-to-github-with-ssh) to set it up. + +Alternatively, you can use HTTPS to clone the repository: + +```bash +git clone https://github.com/turbostarter/core +``` + +Another alternative could be to use the [Github CLI](https://cli.github.com/) or [Github Desktop](https://desktop.github.com/) for Git operations. + + + +## Git remote + +After cloning the repository, remove the original origin remote: + +```bash +git remote rm origin +``` + +Add the upstream remote pointing to the original repository to pull updates: + +```bash +git remote add upstream git@github.com:turbostarter/core +``` + +Once you have your own repository set up, add your repository as the origin: + +```bash +git remote add origin +``` + + + +## Staying up to date + +To pull updates from the upstream repository, run the following command daily (preferably with your morning coffee ☕): + +```bash +git pull upstream main +``` + +This ensures your repository stays up to date with the latest changes. + +Check [Updating codebase](/docs/web/installation/update) for more details on updating your codebase. + + +--- +url: /docs/mobile/installation/commands +title: Common commands +description: Learn about common commands you need to know to work with the mobile project. +--- + + + For sure, you don't need these commands to kickstart your project, but it's useful to know they exist for when you need them. + + + + You can set up aliases for these commands in your shell configuration file. For example, you can set up an alias for `pnpm` to `p`: + + ```bash title="~/.bashrc" + alias p='pnpm' + ``` + + Or, if you're using [Zsh](https://ohmyz.sh/), you can add the alias to `~/.zshrc`: + + ```bash title="~/.zshrc" + alias p='pnpm' + ``` + + Then run `source ~/.bashrc` or `source ~/.zshrc` to apply the changes. + + You can now use `p` instead of `pnpm` in your terminal. For example, `p i` instead of `pnpm install`. + + + + To inject environment variables into the command you run, prefix it with `with-env`: + + ```bash + pnpm with-env + ``` + + For example, `pnpm with-env pnpm build` will run `pnpm build` with the environment variables injected. + + Some commands, like `pnpm dev`, automatically inject the environment variables for you. + + +## Installing dependencies + +To install the dependencies, run: + +```bash +pnpm install +``` + +## Starting development server + +Start development server by running: + +```bash +pnpm dev +``` + +## Building project + +To build the project (including all apps and packages), run: + +```bash +pnpm build +``` + +## Building specific app/package + +To build a specific app/package, run: + +```bash +pnpm turbo build --filter= +``` + +## Cleaning project + +To clean the project, run: + +```bash +pnpm clean +``` + +Then, reinstall the dependencies: + +```bash +pnpm install +``` + +## Formatting code + +To check for formatting errors using Prettier, run: + +```bash +pnpm format +``` + +To fix formatting errors using Prettier, run: + +```bash +pnpm format:fix +``` + +## Linting code + +To check for linting errors using ESLint, run: + +```bash +pnpm lint +``` + +To fix linting errors using ESLint, run: + +```bash +pnpm lint:fix +``` + +## Typechecking + +To typecheck the code using TypeScript for any type errors, run: + +```bash +pnpm typecheck +``` + +## Adding UI components + + + + To add a new web component, run: + + ```bash + pnpm --filter @turbostarter/ui-web ui:add + ``` + + This command will add and export a new component to `@turbostarter/ui-web` package. + + + + To add a new mobile component, run: + + ```bash + pnpm --filter @turbostarter/ui-mobile ui:add + ``` + + This command will add and export a new component to `@turbostarter/ui-mobile` package. + + + +## Services commands + + + To run the services containers locally, you need to have [Docker](https://www.docker.com/) installed on your machine. + + You can always use the cloud-hosted solution (e.g. [Neon](https://neon.tech/), [Turso](https://turso.tech/) for database) for your projects. + + +We have a few commands to help you manage the services containers (for local development). + +### Starting containers + +To start the services containers, run: + +```bash +pnpm services:start +``` + +It will run all the services containers. You can check their configs in `docker-compose.yml`. + +### Setting up services + +To setup all the services, run: + +```bash +pnpm services:setup +``` + +It will start all the services containers and run necessary setup steps. + +### Stopping containers + +To stop the services containers, run: + +```bash +pnpm services:stop +``` + +### Displaying status + +To check the status and logs of the services containers, run: + +```bash +pnpm services:status +``` + +### Displaying logs + +To display the logs of the services containers, run: + +```bash +pnpm services:logs +``` + +### Database commands + +We have a few commands to help you manage the database leveraging [Drizzle CLI](https://orm.drizzle.team/kit-docs/commands). + +#### Generating migrations + +To generate a new migration, run: + +```bash +pnpm with-env turbo db:generate +``` + +It will create a new migration `.sql` file in the `packages/db/migrations` folder. + +#### Running migrations + +To run the migrations against the db, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:migrate +``` + +It will apply all the pending migrations. + +#### Pushing changes directly + + + Make sure you know what you're doing before pushing changes directly to the db. + + +To push changes directly to the db, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:push +``` + +It lets you push your schema changes directly to the database and omit managing SQL migration files. + +#### Checking database status + +To check the status of the database, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:status +``` + +It will display the status of the applied migrations and the pending ones. + +```bash +Applied migrations: +- 0000_cooing_vargas +- 0001_curious_wallflower +- 0002_good_vertigo +- 0003_peaceful_devos +- 0004_fat_mad_thinker +- 0005_yummy_bucky +- 0006_glorious_vargas + +Pending migrations: +- 0007_nebulous_havok +``` + +#### Resetting database + +To reset the database, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:reset +``` + +It will reset the database to the initial state. + +#### Seeding database + +To seed the database with some example data (for development purposes), run: + +```bash +pnpm with-env turbo db:seed +``` + +It will populate your database with some example data. + +#### Checking database + +To check the database schema consistency, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:check +``` + +#### Studying database + +To study the database schema in the browser, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:studio +``` + +This will start the Studio on [https://local.drizzle.studio](https://local.drizzle.studio). + +## Tests commands + +### Running tests + +To run the tests, run: + +```bash +pnpm test +``` + +This will run all the tests in the project using Turbo tasks. As it leverages Turbo caching, it's [recommended](/docs/web/tests/unit#configuration) to run it in your CI/CD pipeline. + +### Running tests projects + +To run tests for all Vitest [Test Projects](https://vitest.dev/guide/projects), run: + +```bash +pnpm test:projects +``` + +This will run all the tests in the project using Vitest. + +### Watching tests + +To watch the tests, run: + +```bash +pnpm test:projects:watch +``` + +This will watch the tests for all [Test Projects](https://vitest.dev/guide/projects) and run them automatically when you make changes. + +### Generating code coverage + +To generate code coverage report, run: + +```bash +pnpm turbo test:coverage +``` + +This will generate a code coverage report in the `coverage` directory under `tooling/vitest` package. + +### Viewing code coverage + +To preview the code coverage report in the browser, run: + +```bash +pnpm turbo test:coverage:view +``` + +This will launch the report's `.html` file in your default browser. + + +--- +url: /docs/mobile/installation/conventions +title: Conventions +description: Some standard conventions used across TurboStarter codebase. +--- + +You're not required to follow these conventions: they're simply a standard set of practices used in the core kit. If you like them - we encourage you to keep these during your usage of the kit - so to have consistent code style that you and your teammates understand. + +## Turborepo Packages + +In this project, we use [Turborepo packages](https://turbo.build/repo/docs/core-concepts/internal-packages) to define reusable code that can be shared across multiple applications. + +* **Apps** are used to define the main application, including routing, layout, and global styles. +* **Packages** shares reusable code add functionalities across multiple applications. They're configurable from the main application. + + + **Recommendation:** Do not create a package for your app code unless you plan to reuse it across multiple applications or are experienced in writing library code. + + If your application is not intended for reuse, keep all code in the app folder. This approach saves time and reduces complexity, both of which are beneficial for fast shipping. + + **Experienced developers:** If you have the experience, feel free to create packages as needed. + + +## Imports and Paths + +When importing modules from packages or apps, use the following conventions: + +* **From a package:** Use `@turbostarter/package-name` (e.g., `@turbostarter/ui`, `@turbostarter/api`, etc.). +* **From an app:** Use `~/` (e.g., `~/components`, `~/config`, etc.). + +## Enforcing conventions + +* [Prettier](https://prettier.io/) is used to enforce code formatting. +* [ESLint](https://eslint.org/) is used to enforce code quality and best practices. +* [TypeScript](https://www.typescriptlang.org/) is used to enforce type safety. + + + + + + + + + +## Code health + +TurboStarter provides a set of tools to ensure code health and quality in your project. + +### Github Actions + +By default, TurboStarter sets up Github Actions to run tests on every push to the repository. You can find the Github Actions configuration in the `.github/workflows` directory. + +The workflow has multiple stages: + +* `format` - runs Prettier to format the code. +* `lint` - runs ESLint to check for linting errors. +* `typecheck` - runs TypeScript to check for type errors. + +### Git hooks + +Together with TurboStarter we have set up a `commit-msg` hook which will check if your commit message follows the [conventional commit](https://www.conventionalcommits.org/en/v1.0.0/) message format. This is important for generating changelogs and keeping a clean commit history. + +Although we didn't ship any pre-commit hooks (we believe in shipping fast with moving checking code responsibility to CI), you can easily add them by using [Husky](https://typicode.github.io/husky/#/). + +#### Setting up the Pre-Commit Hook + +To do so, create a `pre-commit` file in the `./..husky` directory with the following content: + +```bash +#!/bin/sh + +pnpm typecheck +pnpm lint +``` + +Turborepo will execute the commands for all the affected packages - while skipping the ones that are not affected. + +#### Make the Pre-Commit Hook Executable + +```bash +chmod +x ./.husky/pre-commit +``` + +To test the pre-commit hook, try to commit a file with linting errors or type errors. The commit should fail, and you should see the error messages in the console. + + +--- +url: /docs/mobile/installation/dependencies +title: Managing dependencies +description: Learn how to manage dependencies in your project. +--- + +As the package manager we chose [pnpm](https://pnpm.io/). + + + It is a fast, disk space efficient package manager that uses hard links and symlinks to save one version of a module only ever once on a disk. It also has a great [monorepo support](https://pnpm.io/workspaces). Of course, you can change it to use [Bun](https://bunpkg.com), [yarn](https://yarnpkg.com) or [npm](https://www.npmjs.com) with minimal effort. + + +## Install dependency + +To install a package you need to decide whether you want to install it to the root of the monorepo or to a specific workspace. Installing it to the root makes it available to all packages, while installing it to a specific workspace makes it available only to that workspace. + +To install a package globally, run: + +```bash +pnpm add -w +``` + +To install a package to a specific workspace, run: + +```bash +pnpm add --filter +``` + +For example: + +```bash +pnpm add --filter @turbostarter/ui motion +``` + +It will install `motion` to the `@turbostarter/ui` workspace. + +## Remove dependency + +Removing a package is the same as installing but with the `remove` command. + +To remove a package globally, run: + +```bash +pnpm remove -w +``` + +To remove a package from a specific workspace, run: + +```bash +pnpm remove --filter +``` + +## Update a package + +Updating is a bit easier since there is a nice way to update a package in all workspaces at once: + +```bash +pnpm update -r +``` + + + When you update a package, pnpm will respect the [semantic versioning](https://docs.npmjs.com/about-semantic-versioning) rules defined in the `package.json` file. If you want to update a package to the latest version, you can use the `--latest` flag. + + +## Renovate bot + +By default, TurboStarter comes with [Renovate](https://www.npmjs.com/package/renovate) enabled. It is a tool that helps you manage your dependencies by automatically creating pull requests to update your dependencies to the latest versions. You can find its configuration in the `.github/renovate.json` file. Learn more about it in the [official docs](https://docs.renovatebot.com/configuration-options/). + +When it creates a pull request, it is treated as a normal PR, so all tests and preview deployments will run. **It is recommended to always preview and test the changes in the staging environment before merging the PR to the main branch to avoid breaking the application.** + + + + +--- +url: /docs/mobile/installation/development +title: Development +description: Get started with the code and develop your mobile SaaS. +--- + +## Prerequisites + +To get started with TurboStarter, ensure you have the following installed and set up: + +* [Node.js](https://nodejs.org/en) (22.x or higher) +* [Docker](https://www.docker.com) (only if you want to use local services e.g. database) +* [pnpm](https://pnpm.io) +* [Firebase](https://firebase.google.com) project (optional for some features - check [Firebase project](/docs/mobile/installation/firebase) section for more details) + +## Project development + + + + ### Set up environment + + We won't copy the official docs, as there is quite a bit of setup you need to make to get started with iOS and Android development and it also depends what approach you want to take. + + [Check this official setup guide to get started](https://docs.expo.dev/get-started/set-up-your-environment/). After you're done with the setup, go back to this guide and continue with the next step. + + You can pick if you want to develop the app for iOS or Android by using the real device or the simulator. + + + We recommend using the simulators and [development builds](https://docs.expo.dev/develop/development-builds/create-a-build/) for development, as it is more real and reliable approach. It also won't limit you in terms of native dependencies (required for e.g. [analytics](/docs/mobile/analytics/overview)). + + Of course, you can start with the simplest approach (using [Expo Go](https://expo.dev/go)) and when you iterate further, switch to different approach. + + + + + ### Install dependencies + + Install the project dependencies by running the following command: + + ```bash + pnpm i + ``` + + + It is a fast, disk space efficient package manager that uses hard links and symlinks to save one version of a module only ever once on a disk. It also has a great [monorepo support](https://pnpm.io/workspaces). Of course, you can change it to use [Bun](https://bunpkg.com), [yarn](https://yarnpkg.com) or [npm](https://www.npmjs.com) with minimal effort. + + + + + ### Setup environment variables + + Create a `.env.local` files from `.env.example` files and fill in the required environment variables. + + You can use the following command to recursively copy the `.env.example` files to the `.env.local` files: + + + + ```bash + find . -name ".env.example" -exec sh -c 'cp "$1" "${1%.example}.local"' _ {} \; + ``` + + + + ```bash + Get-ChildItem -Recurse -Filter ".env.example" | ForEach-Object { + Copy-Item $_.FullName ($\_.FullName -replace '\.example$', '.local') + } + ``` + + + + Check [Environment variables](/docs/web/configuration/environment-variables) for more details on setting up environment variables. + + + + ### Setup services + + If you want to use local services like database etc. (**recommended for development purposes**), ensure Docker is running, then setup them with: + + ```bash + pnpm services:setup + ``` + + This command initiates the containers and runs necessary setup steps, ensuring your services are up to date and ready to use. + + + + ### Start development server + + To start the application development server, run: + + ```bash + pnpm dev + ``` + + Your development server should now be running at `http://localhost:8081`. + + ![Metro server](/images/docs/mobile/metro-server.png) + + Scan the QR code with your mobile device to start the app or press the appropriate key on your keyboard to run it on simulator. In case of any issues check the [Troubleshooting](https://docs.expo.dev/troubleshooting/overview/) section. + + + + ### Publish to stores + + When you're ready to publish the project to the stores, follow [guidelines](/docs/mobile/marketing) and [checklist](/docs/mobile/publishing/checklist) to ensure everything is set up correctly. + + + + +--- +url: /docs/mobile/installation/editor-setup +title: Editor setup +description: Learn how to set up your editor for the fastest development experience. +--- + +Of course you can use every IDE you like, but you will have the best possible developer experience with this starter kit when using **VSCode-based** editor with the suggested settings and extensions. + +## Settings + +We have included most recommended settings in the `.vscode/settings.json` file to make your development experience as smooth as possible. It include mostly configs for tools like Prettier, ESLint and Tailwind which are used to enforce some conventions across the codebase. You can adjust them to your needs. + +## LLM rules + +We exposed a special endpoint that will scan all the docs and return the content as a text file which you can use to train your LLM or put in a prompt. You can find it at [/llms.txt](/llms.txt). + +The repository also includes a custom rules for most popular AI editors and agents to ensure that AI completions are working as expected and following our conventions. + +### AGENTS.md + +We've integrated specific rules that help maintain code quality and ensure AI-assisted completions align with our project standards. + +You can find them in the `AGENTS.md` file at the root of the project. This format is a standardized way to instruct AI agents to follow project conventions when generating code and it's used by over **20,000** open-source projects - including [Cursor](https://cursor.sh/), [Aider](https://aider.chat/), [Codex from OpenAI](https://openai.com/blog/openai-codex/), [Jules from Google](https://jules.ai/), [Windsurf](https://windsurf.dev/), and many others. + +```md title="AGENTS.md" +### Code Style and Structure + +- Write concise, technical TypeScript code with accurate examples +- Use functional and declarative programming patterns; avoid classes +- Prefer iteration and modularization over code duplication + +### Naming Conventions + +.... +``` + +To learn more about `AGENTS.md` rules check out the [official documentation](https://agents.md/). + +## Extensions + +Once you cloned the project and opened it in VSCode you should be promted to install suggested extensions which are defined in the `.vscode/extensions.json` automatically. In case you rather want to install them manually you can do so at any time later. + +These are the extensions we recommend: + +### ESLint + +Global extension for static code analysis. It will help you to find and fix problems in your JavaScript code. + + + +### Prettier + +Global extension for code formatting. It will help you to keep your code clean and consistent. + + + +### Pretty TypeScript Errors + +Improves TypeScript error messages shown in the editor. + + + +### Tailwind CSS IntelliSense + +Adds IntelliSense for Tailwind CSS classes to enable autocompletion and linting. + + + + +--- +url: /docs/mobile/installation/firebase +title: Firebase project +description: Learn how to set up a Firebase project for your TurboStarter mobile app. +--- + +For some features of your mobile app, you will need to set up a Firebase project. It's a requirement enforced by how these features are implemented under the hood and we cannot change it. + +You would need a Firebase project to use the following features: + +* [Analytics](/docs/mobile/analytics/overview) with [Google Analytics](/docs/mobile/analytics/configuration#google-analytics) provider + +Here, we'll go through the steps to set up a Firebase project and link it to your mobile app. + + + In development environment, the integration with Firebase is possible only when using a [development build](https://docs.expo.dev/workflow/overview/#development-builds). It means that **it won't work in the [Expo Go](https://expo.dev/go) app**. + + + + + ## Create a Firebase project + + First things first, you need to create a Firebase project. You can do this by going to the [Firebase console](https://console.firebase.google.com/) and clicking on "Add Project": + + ![Create a Firebase project](/images/docs/mobile/installation/firebase/create-project.png) + + Name it as you want, and proceed to the dashboard. + + + + ## Install Firebase SDK + + To install React Native Firebase's base app module, run the following command in your mobile app directory: + + ```bash + npx expo install @react-native-firebase/app + ``` + + + + ## Configure Firebase modules + + The recommended approach to configure React Native Firebase is to use [Expo Config Plugins](https://docs.expo.dev/config-plugins/introduction/). + + To enable Firebase on the native Android and iOS platforms, create and download Service Account files for each platform from your Firebase project. + + You can find them in the dashboard under the Firebase project settings: + + ![Download Service Account files](/images/docs/mobile/installation/firebase/config-files.png) + + For Android, it will be a `google-services.json` file, and for iOS it will be a `GoogleService-Info.plist` file. + + Then provide paths to the downloaded files in the following `app.config.ts` fields: [`android.googleServicesFile`](https://docs.expo.io/versions/latest/config/app/#googleservicesfile-1) and [`ios.googleServicesFile`](https://docs.expo.io/versions/latest/config/app/#googleservicesfile). This is how an example configuration looks like: + + ```ts title="app.config.ts" + export default ({ config }: ConfigContext): ExpoConfig => ({ + ...config, + ios: { + googleServicesFile: "./GoogleService-Info.plist", + }, + android: { + googleServicesFile: "./google-services.json", + }, + plugins: [ + "@react-native-firebase/app", + [ + "expo-build-properties", + { + ios: { + useFrameworks: "static", + }, + }, + ], + ], + }); + ``` + + + For iOS only, since `firebase-ios-sdk` requires `use_frameworks` you need to configure `expo-build-properties` by adding `"useFrameworks": "static"`. + + + Listing a module in the Config Plugins (the `plugins` array in the config above) is only required for React Native Firebase modules that involve native installation steps - e.g. modifying the Xcode project, `Podfile`, `build.gradle`, `AndroidManifest.xml` etc. React Native Firebase modules without native steps will work out of the box. + + + + ## Generate native code + + If you are compiling your app locally, you'll need to regenerate the native code for the platforms to pick up the changes: + + ```bash + npx expo prebuild --clean + ``` + + Then, you could follow the same steps as in the [development environment setup](/docs/mobile/installation/development) guide to run the app locally or [build a production version](/docs/mobile/publishing/checklist#build-your-app) of your app. + + + +Et voilà! You've set up and linked your Firebase project to your mobile app 🎉 + +You can learn more about the Firebase integration and it's possibilities in the [official documentation](https://rnfirebase.io/). + + +--- +url: /docs/mobile/installation/structure +title: Project structure +description: Learn about the project structure and how to navigate it. +--- + +The main directories in the project are: + +* `apps` - the location of the main apps +* `packages` - the location of the shared code and the API + +### `apps` Directory + +This is where the apps live. It includes web app (Next.js), mobile app (React Native - Expo), and the browser extension (WXT - Vite + React). Each app has its own directory. + +### `packages` Directory + +This is where the shared code and the API for packages live. It includes the following: + +* shared libraries (database, mailers, cms, billing, etc.) +* shared features (auth, mails, billing, ai etc.) +* UI components (buttons, forms, modals, etc.) + +All apps can use and reuse the API exported from the packages directory. This makes it easy to have one, or many apps in the same codebase, sharing the same code. + +## Repository structure + +By default the monorepo contains the following apps and packages: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +## Mobile application structure + +The mobile application is located in the `apps/mobile` folder. It contains the following folders: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +--- +url: /docs/mobile/installation/update +title: Updating codebase +description: Learn how to update your codebase to the latest version. +--- + +If you've been following along with our previous guides, you should already have a Git repository set up for your project, with an `upstream` remote pointing to the original repository. + +Updating your project involves fetching the latest changes from the `upstream` remote and merging them into your project. Let's dive into the steps! + + + + ## Stash changes + + + If you don't have any changes to stash, you can skip this step and proceed with the update process. + + Alternatively, you can [commit](https://git-scm.com/docs/git-commit) your changes. + + + If you have any uncommitted changes, stash them before proceeding. It will allow you to avoid any conflicts that may arise during the update process. + + ```bash + git stash + ``` + + This command will save your changes in a temporary location, allowing you to retrieve them later. Once you're done updating, you can apply the stash to your working directory. + + ```bash + git stash apply + ``` + + + + ## Pull changes + + Pull the latest changes from the `upstream` remote. + + ```bash + git pull upstream main + ``` + + When prompted the first time, please opt for merging instead of rebasing. + + Don't forget to run `pnpm i` in case there are any updates in the dependencies. + + + + ## Resolve conflicts + + If there are any conflicts during the merge, Git will notify you. You can resolve them by opening the conflicting files in your code editor and making the necessary changes. + + + If you find conflicts in the `pnpm-lock.yaml file`, accept either of the two changes (avoid manual edits), then run: + + ```bash + pnpm i + ``` + + Your lock file will now reflect both your changes and the updates from the upstream repository. + + + + + ## Run a health check + + After resolving the conflicts, it's time to test your project to ensure everything is working as expected. Run your project locally and navigate through the various features to verify that everything is functioning correctly. + + For a quick health check, you can run: + + ```bash + pnpm lint + pnpm typecheck + + ``` + + If everything looks good, you're all set! Your project is now up to date with the latest changes from the `upstream` repository. + + + + ## Commit and push + + Once everything is working fine, don't forget to commit your changes using: + + ```bash + git commit -m "" + ``` + + and push them to your remote repository with: + + ```bash + git push origin + ``` + + + + +--- +url: /docs/mobile/internationalization +title: Internationalization +description: Learn how to internationalize your mobile app. +--- + +TurboStarter mobile uses [i18next](https://www.i18next.com/) and [expo-localization](https://docs.expo.dev/versions/latest/sdk/localization/) for internationalization. This powerful combination allows you to leverage both i18next's mature translation framework and Expo's native device locale detection. + + + While i18next handles the translation management, expo-localization provides + seamless integration with the device's locale settings. This means your app + can automatically detect and adapt to the user's preferred language, while + still maintaining the flexibility to override it when needed. + + +The mobile app's internationalization is configured to work out of the box with: + +* Automatic device language detection +* Right-to-left (RTL) layout support +* Locale-aware date and number formatting +* Fallback language handling + +You can read more about the underlying technologies in their documentation: + +* [i18next documentation](https://www.i18next.com/overview/getting-started) +* [expo-localization documentation](https://docs.expo.dev/versions/latest/sdk/localization/) + +![i18next logo](/images/docs/i18next.jpg) + +## Configuration + +The global configuration is defined in the `@turbostarter/i18n` package and shared across all applications. You can read more about it in the [web configuration](/docs/web/internationalization/configuration) documentation. + +By default, the locale is automatically detected based on the user's device settings. You can override it and set the default locale of your mobile app in the [app configuration](/docs/mobile/configuration/app) file. + +## Translating app + +To translate individual components and screens, you can use the `useTranslation` hook. + +```tsx +import { useTranslation } from "@turbostarter/i18n"; + +export default function MyComponent() { + const { t } = useTranslation(); + + return {t("hello")}; +} +``` + +It's a recommended way to translate your app. + +### Store presence + +If you plan on shipping your app to different countries or regions or want it to support various languages, you can provide localized strings for things like the display name and system dialogs. + +To do so, check the [official Expo documentation](https://docs.expo.dev/guides/localization/) as it requires modifying your app configuration (`app.config.ts`). + +You can find the resources below helpful in this process: + + + + + + + + + +## Language switcher + +TurboStarter ships with a language customizer component that allows you to switch between languages. You can import and use the `LocaleCustomizer` component and drop it anywhere in your application to allow users to change the language seamlessly. + +```tsx +import { LocaleCustomizer } from "@turbostarter/ui-mobile/i18n"; + +export default function MyComponent() { + return ; +} +``` + +The component automatically displays all languages configured in your i18n settings. When a user switches languages, it will be reflected in the app and saved into persistent storage to keep the language across app restarts. + +## Best practices + +Here are key best practices for managing translations in your mobile app: + +* Use clear, hierarchical translation keys for easy maintenance + + ```ts + // ✅ Good + "screen.home.welcome"; + "component.button.submit"; + + // ❌ Bad + "welcomeText"; + ``` + +* Organize translations by app screens and features + + ``` + translations/ + ├── en/ + │ ├── layout.json + │ └── common.json + └── es/ + ├── layout.json + └── common.json + ``` + +* Consider device language settings and regional formats + +* Cache translations locally for offline access + +* Handle dynamic content for mobile contexts: + + ```ts + // Device-specific messages + t("errors.noConnection"); // "Check your internet connection" + + // Dynamic values + t("storage.space", { gb: 2.5 }); // "2.5 GB available" + ``` + +* Keep translations concise - mobile screens have limited space + +* Test translations with different screen sizes and orientations + + +--- +url: /docs/mobile/marketing +title: Marketing +description: Learn how to market your mobile application. +--- + +As you saw in the [Extras](/docs/mobile/extras) section, TurboStarter comes with a lot of tips and tricks to make your product better and help you launch your app faster with higher traffic. + +The same applies to [submission tips](/docs/mobile/extras#submission-tips) to help you get your app approved by Apple and Google faster. + +We'll talk more about the whole process of deploying and publishing your app in the [Publishing](/docs/mobile/publishing/checklist) section, here we'll go through some guidelines that you need to follow to make your store's visibility higher. + +## Before you submit + +To help your app approval go as smoothly as possible, review the common missteps listed below that can slow down the review process or trigger a rejection. This doesn't replace the official guidelines or guarantee approval, but making sure you can check every item on the list is a good start. + +Make sure you: + +* Test your app for crashes and bugs +* Ensure that all app information and metadata is complete and accurate +* Update your contact information in case App Review needs to reach you +* Provide App Review with full access to your app. If your app includes account-based features, provide either an active demo account or fully-featured demo mode, plus any other hardware or resources that might be needed to review your app (e.g. login credentials or a sample QR code) +* Enable backend services so that they're live and accessible during review +* Include detailed explanations of non-obvious features and in-app purchases in the App Review notes, including supporting documentation where appropriate + +Following these basic steps during development and before submission will help you get your app approved faster. + +## App Store (iOS) + +Apple reviews are much stricter than Google reviews, so you need to make sure your app is ready for the App Store. + +### Guidelines + +Apple has a set of [guidelines](https://developer.apple.com/app-store/review/guidelines/) that you need to follow to make sure your app can be accepted in the App Store. + +These include: + +* **Safety**: Your app must not contain content or behavior that is harmful, abusive, or threatening. +* **Performance**: Your app must be performant and stable, with a smooth user experience. +* **Business**: Your app must not engage in unethical or deceptive practices. +* **Design**: Your app must have a clean and intuitive design. +* **Legal**: Your app must comply with all relevant laws and regulations. + +You can read more about each guideline in the [official App Review Guidelines](https://developer.apple.com/app-store/review/guidelines/). + +### Search optimization + +App store optimization is the process of increasing an app or game's visibility in an app store, with the objective of increasing organic app downloads. Apps are more visible when they rank high on a wide variety of search terms, maintain a high position in the top charts, or get featured on the store. + +There are a few actions that you can take to improve your app's visibility in the App Store: + +* **Choose accurate keywords**: Use relevant keywords in your app's store listing. +* **Create a compelling app name, subtitle, and description**: Your app's title should be catchy and descriptive, the same applies to the subtitle and description. +* **Assign the right categories**: Make sure your app is categorized in the right category, this will help you reach the right audience. +* **Foster positive ratings**: Ratings and reviews appear on your product page and influence how your app ranks in search results. They can encourage people to engage with your app, so focus on providing a great app experience that motivates users to leave positive reviews. +* **Publish in-app events**: You can publish in-app events to promote your app and encourage users to engage with your app. (e.g. game competitions) +* **Promote in-app purchases**: Your promoted in-app purchases appear in search results on the App Store. Tapping an in-app purchase leads to your product page, which displays your app's description, screenshots, app previews, and in-app events — and lets people initiate an in-app purchase. + +Read more about App Store Optimization in the [official documentation](https://developer.apple.com/app-store/search/). + + + + + + + +## Google Play (Android) + +Google reviews are less stringent than Apple reviews and usually take less time to review, but you still need to make sure your app is ready for the Play Store. + +### Guidelines + +Google has its own guidelines that apps must adhere to. Some important aspects to consider include: + +* **Spam, functionality, and user experience**: Your app must not be spammy, must work as expected and must provide a good user experience. +* **Restricted content**: Before submitting an app to Google Play, ensure it complies with these content policies and with local laws. +* **Privacy**: Apps that are deceptive, malicious, or intended to abuse or misuse any network, device, or personal data are strictly prohibited +* **Monetization**: Your app must not engage in unethical or deceptive practices. + +For more detailed information and an interactive checklist, check the [Google requirements page](https://developers.google.com/workspace/marketplace/about-app-review). + +### Search optimization + +Ensuring that your app and store listing is thorough and optimized is an important factor in getting discovered by users on Google Play. + +Follow these steps to optimize your app's visibility on Google Play: + +* **Build a comprehensive store listing**: This includes providing accurate **title**, **description** and **promo text**. +* **Use high-quality graphics and images**: App icons, images, and screenshots help make your app stand out in search results, categories, and featured app lists. +* **Diversify your audience**: Google provides automated machine translations of store listings that you don't explicitly define for your app. However, using a professional translation service for your *Description* can lead to better search results and discoverability for worldwide users. +* **Create a great user experience**: Google Play search factors in the overall experience of your app based on user behavior and feedback. Apps are ranked based on a combination of ratings, reviews, downloads, and other factors. + + + + + + + +## Common mistakes + +There are a few common mistakes that you should avoid to make sure your app can be accepted in the stores. Apple reports that, on average, over **40%** of unresolved issues relate to [guideline 2.1: App Completeness](https://developer.apple.com/app-store/review/guidelines/#2.1), so make sure to avoid these: + +* **Crashes and bugs** +* **Broken links** +* **Placeholder content** +* **Incomplete information** +* **Privacy policy issues** +* **Inaccurate screenshots** +* **Repeated submission of similar apps** + +Don't worry if your first submission is rejected, improve it, fix all the mentioned issues and try again. + + +--- +url: /docs/mobile/monitoring/overview +title: Overview +description: Get started with mobile monitoring in TurboStarter. +--- + +TurboStarter ships with powerful, provider-agnostic monitoring helpers for the mobile app so you can answer the questions that matter in production: **what broke**, **on which screen**, and **which users were impacted**. It's designed for simplicity and extensibility, and works with multiple providers behind a single API. + +## Capturing exceptions + +On mobile, you'll usually want to report errors from a few key places: + +* **UI/runtime crashes**: unexpected JS errors that would otherwise blank the screen or break navigation. +* **Async work**: background tasks, effects, and data fetching where failures are easy to miss. +* **Manual reporting**: wrap critical flows (auth, purchases, sync, deep-links) with `try/catch` so you can attach context when things go wrong. + +```tsx +import { Pressable, Text } from "react-native"; +import { captureException } from "@turbostarter/monitoring-mobile"; + +export default function ExampleComponent() { + const handleClick = () => { + try { + /* some risky operation */ + } catch (error) { + captureException(error); + } + }; + + return ( + + Trigger Exception + + ); +} +``` + + + `try/catch` (and most JS error handlers) can only see JavaScript exceptions. Native crashes (for example, a hard crash in a native module) typically require provider-specific native setup to capture crash reports. Use the provider pages below for platform details. + + +## Identifying users + +Error reports become much more actionable once they're tied to a signed-in user. TurboStarter supports identifying the current user after the auth session resolves, so your monitoring provider can associate errors with a stable user profile (without you plumbing this through every capture call). + +If you want richer filtering, pass non-sensitive traits (plan, role, locale) depending on what your provider supports. + +```tsx title="monitoring.tsx" +import { useEffect } from "react"; +import { identify } from "@turbostarter/monitoring-mobile"; +import { authClient } from "~/lib/auth"; + +export const MonitoringProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const session = authClient.useSession(); + + useEffect(() => { + if (session.isPending) { + return; + } + + identify(session.data?.user ?? null); + }, [session]); + + return children; +}; +``` + + + Identify users with **stable IDs** and only the traits you need for debugging. Avoid sending PII or secrets (tokens, raw emails, payment details) unless you've explicitly decided it's acceptable for your monitoring provider and compliance requirements. + + +## Providers + +TurboStarter can report through different monitoring providers while keeping your app code consistent. Choose a provider (or swap later) by updating the exports/config in the monitoring package. + + + + + + + +## Recommended practices + + + + Prioritize crashes, failed network calls that break a flow, and unexpected + states. Skip noisy “expected” errors (validation, user cancellations). + + + + Include the screen/route, the action the user took, and relevant IDs + (request id, order id). Mobile issues are often device- or version-specific, + so make sure app version/build info is included by your provider. + + + + If an effect or retry path can fire repeatedly, debounce or dedupe your + capture calls so you don't spam reports (or exceed quotas). + + + + Keep environments isolated so test devices don't pollute production signal. + Tag builds/releases so you can correlate spikes with deployments. + + + +With solid capture + identification in place, mobile monitoring becomes a feedback loop: you can spot regressions quickly, understand who they affect, and validate fixes by release. + + +--- +url: /docs/mobile/monitoring/posthog +title: PostHog +description: Learn how to setup PostHog as your mobile monitoring provider. +--- + +[PostHog](https://posthog.com/) is a product analytics platform that can also help with monitoring via error tracking and session replay. On mobile, it's especially useful when you want to connect **what went wrong** with **what the user did** right before it happened. + +TurboStarter keeps monitoring provider selection behind a unified API, so you can route captures to PostHog without changing your app code. + + + You'll need a PostHog account ([cloud](https://app.posthog.com/signup) or [self-hosted](https://posthog.com/docs/self-host)) to use it as your monitoring provider. + + + + PostHog is one of the preconfigured analytics providers for mobile apps. If you want product analytics (events, screens, funnels), see [analytics overview](/docs/mobile/analytics/overview) and the [PostHog configuration](/docs/mobile/analytics/configuration#posthog). + + +![Posthog banner](/images/docs/web/monitoring/posthog/banner.jpg) + +## Configuration + +PostHog makes it easy to monitor your mobile app for errors and issues, giving you full visibility into when things go wrong. With TurboStarter, you can enable PostHog-based monitoring in just a few steps, sending errors and related user actions to your PostHog dashboard for debugging and product improvement. + + + + ### Create a project + + Create a new PostHog [project](https://app.posthog.com/project/settings) for your mobile app. You can do this from the [PostHog dashboard](https://app.posthog.com) using the *New Project* action. + + + + ### Activate PostHog as your monitoring provider + + TurboStarter chooses the mobile monitoring provider through exports in `packages/monitoring/mobile`. To route monitoring events to PostHog, export the PostHog implementation from the package entrypoint: + + ```ts title="index.ts" + // [!code word:posthog] + export * from "./posthog"; + export * from "./posthog/env"; + ``` + + + + ### Set environment variables + + Add your PostHog project key (and host, if you're not using the default cloud region) to your mobile app env. Set these locally and in your build environment (for example, in your [EAS build profile](/docs/mobile/publishing/checklist#environment-variables)): + + ```dotenv title="apps/mobile/.env.local" + EXPO_PUBLIC_POSTHOG_KEY="your-posthog-project-api-key" + EXPO_PUBLIC_POSTHOG_HOST="https://us.i.posthog.com" + ``` + + + +That's it - launch the app, trigger an error, and confirm events are arriving in your PostHog project. + +![Posthog error](/images/docs/web/monitoring/posthog/error.png) + +If you want to go beyond basic capture (session replay, feature flags, richer device/session context), follow [PostHog's React Native setup guidance](https://posthog.com/docs/error-tracking/installation/react-native). + + + + + + + +## Uploading source maps + +**Source maps** map the bundled/minified JavaScript running on devices back to your original source code. Without them, mobile stack traces are often hard to read and difficult to action. + + + With source maps uploaded to PostHog, error reports can be symbolicated so stack traces point to the real files and line numbers from your project. + + +PostHog's React Native source maps flow has two main parts: + +* **Inject debug IDs** into the bundle during bundling (Metro) +* **Upload source maps** during your iOS/Android build (or via CLI in CI) + + + + ### Install and authenticate the PostHog CLI + + Install the CLI globally: + + ```bash + npm install -g @posthog/cli + ``` + + Then authenticate: + + ```bash + posthog-cli login + ``` + + If you're running in CI, you can authenticate with environment variables instead: + + ```dotenv + POSTHOG_CLI_HOST="https://us.posthog.com" + POSTHOG_CLI_ENV_ID="your-posthog-project-id" + POSTHOG_CLI_TOKEN="your-personal-api-key" + ``` + + + + ### Inject debug IDs with Metro + + Automatic injection relies on Expo's debug ID support. Update `metro.config.js` to use PostHog's Expo config: + + ```js title="metro.config.js" + const { getPostHogExpoConfig } = require("posthog-react-native/metro"); + + const config = getPostHogExpoConfig(__dirname); + + module.exports = config; + ``` + + + + ### Upload source maps during builds + + If you can use the Expo plugin (recommended for managed EAS builds), add the plugin to your Expo config: + + ```ts title="app.config.ts" + export default ({ config }: ConfigContext): ExpoConfig => ({ + ...config, + plugins: ["posthog-react-native/expo"], + }); + ``` + + If you can't use the Expo plugin, PostHog also supports wiring uploads directly into: + + * **Android**: your Gradle build (`android/app/build.gradle`) + * **iOS**: your Xcode “Bundle React Native code and images” build phase + + Follow the [official PostHog instructions](https://posthog.com/docs/error-tracking/upload-source-maps/react-native) for the exact snippets for each platform. + + + + ### Verify uploads in PostHog + + After a release build, confirm your symbol sets are present in [PostHog project error tracking dashboard](https://app.posthog.com/settings/project-error-tracking#error-tracking-symbol-sets) and then trigger a test error to ensure stack traces are resolving as expected. + + + +With debug IDs injected and source maps uploaded, PostHog can symbolicate React Native errors so stack traces point back to your original source files. If traces still look minified, double-check that you're testing a release build and that the latest symbol sets are present in your project settings. + + + + + + + + +--- +url: /docs/mobile/monitoring/sentry +title: Sentry +description: Learn how to setup Sentry as your mobile monitoring provider. +--- + +[Sentry](https://sentry.io/) is a popular error monitoring platform that captures crashes and exceptions from production devices and helps you debug them with stack traces, breadcrumbs, and user context. + +TurboStarter's mobile monitoring layer is provider-agnostic, but Sentry is a great default when you want reliable crash reporting plus readable stack traces in release builds. + + + To use Sentry, create an [account in Sentry](https://sentry.io/signup) first. + + +![Sentry banner](/images/docs/web/monitoring/sentry/banner.png) + +## Configuration + +TurboStarter integrates effortlessly with Sentry, so you can capture application errors and analyze performance from development through production. Setting up Sentry as your provider lets you quickly find and fix issues, contributing to a more robust and dependable app. + +Follow the steps below to integrate Sentry with your TurboStarter project. + + + + ### Create a project + + Begin by creating a [project](https://docs.sentry.io/product/projects/) in Sentry. You can set this up from your [dashboard](https://sentry.io/settings/account/projects/) by clicking the *Create Project* button. + + + + ### Activate Sentry as your monitoring provider + + The monitoring provider to use is determined by the exports in `packages/monitoring/mobile` package. To activate Sentry as your monitoring provider, you need to update the exports in: + + ```ts title="index.ts" + // [!code word:sentry] + export * from "./sentry"; + export * from "./sentry/env"; + ``` + + If you want to customize the provider, you can find its definition in `packages/monitoring/mobile/src/providers/sentry` directory. + + + + ### Set environment variables + + Based on your [project settings](https://sentry.io/project/settings), fill the following environment variables in your `.env.local` file in `apps/mobile` directory and your deployment environment (e.g. [EAS build profile](/docs/mobile/publishing/checklist#environment-variables)): + + ```dotenv title="apps/mobile/.env.local" + EXPO_PUBLIC_SENTRY_DSN="your-sentry-dsn" + EXPO_PUBLIC_PROJECT_ENVIRONMENT="your-project-environment" + ``` + + + + ### Wrap your app + + Install the Sentry React Native SDK in the `mobile` workspace. + + ```bash + pnpm i @sentry/react-native --filter mobile + ``` + + And then wrap the root component of your application with Sentry.wrap: + + ```tsx title="app/_layout.tsx" + import * as Sentry from "@sentry/react-native"; + + export default Sentry.wrap(RootLayout); + ``` + + + TurboStarter initializes the SDK for you based on env + provider exports; you only need to wrap the root component. + + + + +You're all set! Start your app and view any errors or exceptions directly in your [Sentry dashboard](https://sentry.io/settings/account/projects/). + +![Sentry error](/images/docs/web/monitoring/sentry/error.jpg) + +You can tailor the setup further if needed. For more details, refer to the [official Sentry documentation](https://docs.sentry.io/platforms/react-native/features/). + + + + + + + +## Uploading source maps + +Readable stack traces in Sentry require uploading source maps for release builds. For Expo projects, Sentry recommends enabling **two pieces**: + +* the **Sentry Expo config plugin** (uploads during native builds) +* the **Sentry Metro plugin** (adds debug IDs so bundles and source maps match) + +### Add the Sentry Expo plugin + +Add `@sentry/react-native/expo` plugin to your Expo config (`app.config.ts`): + +```ts title="app.config.ts" +export default ({ config }: ConfigContext): ExpoConfig => ({ + ...config, + plugins: [ + [ + "@sentry/react-native/expo", + { + url: "https://sentry.io/", + project: "your-sentry-project", + organization: "your-sentry-organization", + }, + ], + ], +}); +``` + +Then provide an auth token through environment variables (locally in `.env.local` file in `apps/mobile` directory) and your build environment: + +```dotenv title="apps/mobile/.env.local" +SENTRY_AUTH_TOKEN="your-sentry-auth-token" +``` + +### Add the Sentry Metro plugin + +To ensure unique Debug IDs are assigned to the generated bundles and source maps, add the Sentry Metro Plugin to the configuration. + +Update `metro.config.js` to use `getSentryExpoConfig`: + +```js title="metro.config.js" +const { getSentryExpoConfig } = require("@sentry/react-native/metro"); + +const config = getSentryExpoConfig(__dirname); + +module.exports = config; +``` + +With the Expo plugin + Metro plugin in place, source maps are uploaded automatically during release native builds and EAS builds (debug builds typically rely on Metro's symbolication). + +Take a moment to test your setup by triggering an error in your app, then confirm that source maps are resolving stack traces accurately in your [Sentry dashboard](https://sentry.io/settings/account/projects/). For advanced setup details, troubleshooting, or further customization with React Native and Expo, refer to the [official Sentry documentation](https://docs.sentry.io/platforms/react-native/guides/expo/sourcemaps/). + + + + + + + + +--- +url: /docs/mobile/organizations/active-organization +title: Active organization +description: Set and switch the current organization context within your application. +--- + +The active organization on mobile mirrors the behavior used on the [web app](/docs/web/organizations/active-organization) and in the [extension](/docs/extension/organizations). It is tracked in the authenticated session as `activeOrganizationId` and used to scope all organization-bound data and actions. + +Below you can find how to read and work with the active organization in your mobile app context. + +## Reading the active organization + +Use your auth client's helper to read the active organization from the session. This keeps the client in sync with the server and avoids duplicating tenancy logic. + +```tsx title="organizations.tsx" +import { authClient } from "~/lib/auth"; + +export function OrganizationsScreen() { + const organization = authClient.useActiveOrganization(); + const member = authClient.useActiveMember(); + + return ( + <> + {organization?.name} + {member?.role} + + ); +} +``` + +This mirrors the [extension](/docs/extension/organizations) approach and the [web hook](/docs/web/organizations/active-organization), ensuring the active organization and member role stay consistent with the server session. + +## Performing actions + +When invoking API routes from the mobile app, prefer passing the `organizationId` explicitly with the payload. This guarantees the correct tenant is targeted even if multiple devices or views are active simultaneously. + +```tsx title="create-post.tsx" +import { api } from "~/lib/api"; + +export function CreatePost() { + const activeOrganization = authClient.useActiveOrganization(); + + const { mutate } = useMutation({ + mutationFn: async (post: PostInput) => + api.posts.$post({ + ...post, + organizationId: activeOrganization?.id, + }), + }); + + return ( +
+ +
+ ); +} +``` + +This mirrors the recommendation from the [web guide](/docs/web/organizations/active-organization#api-route) and avoids edge cases tied to stale session values. + +## Switching organizations + +TurboStarter ships an account switcher out of the box for mobile. You can drop it into your app and customize labels and styling as needed. + +```tsx title="settings.tsx" +import { AccountSwitcher } from "~/modules/organization/account-switcher"; + +export function SettingsScreen() { + return ; +} +``` + +When a user selects a new organization, it calls your backend to update the session's `activeOrganizationId` and then re-read the session or invalidate related queries. + +For deeper background on how the active organization is resolved, see the [web guide](/docs/web/organizations/active-organization). + + +--- +url: /docs/mobile/organizations/invitations +title: Invitations +description: Send, track, and accept organization invites. +--- + +Invite teammates by email to join an organization directly from your mobile app. Acceptance is straightforward: we verify the invite, create or reuse the membership with the intended role, and set the user's active organization. + +The implementation uses the same APIs and rules as the [web app](/docs/web/organizations/invitations) and is powered by the [Better Auth organization plugin](https://www.better-auth.com/docs/plugins/organization). + +![Mobile invitations list](/images/docs/mobile/organizations/invitations/list.png) + +## Capabilities + +* Send invitations by email. +* View and filter invitations by status or role, and search by email. +* Resend or revoke an invitation. +* Accept an invitation via a [deep link](https://docs.expo.dev/linking/into-your-app/). + + + Permissions are enforced by roles. Typically, only organization admins can + send or manage invites. See [RBAC (Roles & + Permissions)](/docs/mobile/organizations/rbac). + + +## Inviting members + +Sending an invitation typically requires the invitee's email and the intended role. You can add multiple recipients in the invitation form to invite several members at once. + +![Invite members bottom sheet](/images/docs/mobile/organizations/invitations/invite.png) + +After sending, the invitee receives an email with a link to accept. It's a [deep link](https://docs.expo.dev/guides/linking) that opens your app and automatically validates the invite. + +## Handling invitations + +When a recipient opens an invite link on their device, the app automatically handles the entire flow - reading, parsing, and validating the invite - for you. + +![Join organization prompt](/images/docs/mobile/organizations/invitations/join.png) + +When the user accepts, we create or reuse their membership and set the active organization in their session. If they reject the invite, we redirect them to their account home. + +## Learn more + +For underlying details shared across platforms, see the web documentation: + + + + + + + + + + + +These cover the schema, token lifecycle, and admin tooling shared by the mobile and web apps. + + +--- +url: /docs/mobile/organizations/overview +title: Overview +description: Learn how to use organizations/teams/multi-tenancy in TurboStarter mobile app. +--- + +Organizations let you build teams and multi-tenant SaaS out of the box in the mobile app. + +Users can create organizations, invite teammates, assign roles, and seamlessly switch between workspaces — all from iOS/Android — with the same secure data isolation used on the [web app](/docs/web/organizations/overview). + + + [Multi-tenancy](https://www.ibm.com/think/topics/multi-tenant) is a software architecture pattern where a single instance of an application serves multiple tenants, each with its own data and configuration. + + +The feature is powered by the same [Better Auth organization plugin](https://www.better-auth.com/docs/plugins/organization) and shares TurboStarter's API, routing, and data layer with the [web app](/docs/web/organizations/overview) and [extension](/docs/extension/organizations). That means your mobile app benefits from the same tenancy rules, RBAC checks, and invitations flow without duplicating backend logic. + + + +## Architecture + +On mobile, TurboStarter uses the same pragmatic multi-tenant architecture as the [web app](/docs/web/organizations/overview): + +* **Tenant context** lives in the authenticated session as the active organization ID. The mobile client reads this context from the API and includes it when making requests. +* **Data scoping** is performed server-side via `organizationId` on tenant-owned tables and guard clauses in queries. Mobile screens consume scoped endpoints so users only see data for their selected organization. +* **Authorization** combines tenant scoping with role checks. We separate “can access this tenant?” from “can perform this action within the tenant?”. +* **Extensibility**: add new tenant-bound entities by including `organizationId` in your schema and using the provided helpers to read or switch the active organization in the app. + +This keeps data isolated per organization while remaining simple to reason about across platforms. + + + For deeper details on the shared data model used by the mobile app, see [Data + model](/docs/web/organizations/data-model). + + +## Concepts + +The same core concepts apply in the mobile app: + +| Concept | Description | +| ----------------------- | -------------------------------------------------------------------------------------------------- | +| **Organization** | A workspace that owns resources and settings, acting as an isolated tenant. | +| **Member** | A user assigned to an organization. | +| **Role** | Access level within an organization (see [RBAC](/docs/mobile/organizations/rbac)). | +| **Invitation** | Email request to join an organization (see [Invitations](/docs/mobile/organizations/invitations)). | +| **Active organization** | The currently selected organization in a user's session, used to scope data and permissions. | + +These concepts provide the building blocks for flexible team management and secure, multi-tenant SaaS applications on mobile. + +## Development data + +In development, TurboStarter automatically [seeds](/docs/mobile/installation/commands#seeding-database) example data when you set up services. The mobile app connects to the same development API, so you can test the full organizations flow end-to-end: + +* One organization is created by default. +* All default roles are created and assigned within that organization. +* Sample invitations are generated so you can test the invite flow. + +You can safely experiment with these sample organizations, roles, and invitations to understand multi-tenancy features — [reset](/docs/mobile/installation/commands#resetting-database) or [reseed](/docs/mobile/installation/commands#seeding-database) anytime to return to the default state. + +The default credentials for demo users can be customized using the `SEED_EMAIL` and `SEED_PASSWORD` environment variables. + + + The default development data and setup are intended for local development and + testing only. **Never** use these seeds or configurations in a production + environment - they are insecure and may expose sensitive functionality. + + +## Customization + +You have flexibility to adapt organizations to fit your mobile experience. For example, you might rename labels (such as Organization to *Team* or *Workspace*), and update the app copy accordingly. + +You can adjust the available [roles and permissions](/docs/mobile/organizations/rbac) to suit your access model. + +The [invitation flow](/docs/mobile/organizations/invitations) can be customized, including how verification, onboarding, or metadata capture work. + +Feel free to check how to configure all of these features inside mobile application in the dedicated sections linked above. + + +--- +url: /docs/mobile/organizations/rbac +title: RBAC (Roles & Permissions) +description: Manage roles, permissions, and access scopes. +--- + +Role-based access control (RBAC) lets you define who can do what in an organization. + + + If you're new to the RBAC concept, a simple mental model is: + + * Users belong to organizations. + * Users get roles. + * Roles map to permissions on resources. + + +In TurboStarter, we primarily rely on the [Better Auth plugin](https://www.better-auth.com/docs/plugins/organization) for the heavy lifting—roles, permissions, teams, and member management—while handling critical logic with our own code. + +This provides a flexible access control system, letting you control user access based on their role in the organization. You can also define custom permissions per role. + + + TurboStarter ships with the default RBAC system configured out of the box. This setup may be enough if you're not planning a very complex access control system, but you can also easily customize it to your needs. + + On mobile, use conditional UI (disable or hide actions) together with client helpers to match each member's role. + + +## Roles + +Roles are named bundles of permissions. Keep them few and well-defined. By default, we have the following roles: + +```ts +const MemberRole = { + MEMBER: "member", + ADMIN: "admin", + OWNER: "owner", +} as const; +``` + +A user can have multiple roles in an organization. For example, a user can be a member and an admin (if it makes sense for your application). + + + The organization's `admin` role is different from the user's global `admin` role. + + The organization `admin` governs permissions only inside the organization, whereas the global `admin` controls access to the [super admin dashboard](/docs/web/admin/overview). + + +To create additional roles with custom permissions, see the [official documentation](https://www.better-auth.com/docs/plugins/organization#create-access-control) for more details. + +## Permissions + +Permissions represent what actions a role can perform on which resources. + +To check if the current user has permission to perform an action on mobile, use the client helper and handle the boolean result in your component logic. + +```tsx title="create-project.tsx" +import { useQuery, useMutation } from "@tanstack/react-query"; +import { authClient } from "~/lib/auth"; + +export function CreateProject() { + const { data: canCreate } = useQuery({ + queryKey: ["permission", "project", "create"], + queryFn: () => + authClient.organization.hasPermission({ + permissions: { project: ["create"] }, + }), + }); + + const { mutate, isPending } = useMutation({ + mutationFn: async () => { + // perform the create action + }, + }); + + return ( + + ); +} +``` + +When you already have the active member's role, prefer the client-side `checkRolePermission` to avoid extra API calls. + +```tsx title="update-project.tsx" +import { authClient } from "~/lib/auth"; + +export function UpdateProject() { + const activeMember = authClient.useActiveMember(); + + const canUpdate = authClient.organization.checkRolePermission({ + permission: { + project: ["update"], + }, + role: activeMember.role, + }); + + return ; +} +``` + +We leverage the existing hook to retrieve the active member role within the [active organization](/docs/mobile/organizations/active-organization) context. That way, you can easily check whether a member has permission to perform an action without a server round trip. + + + This does not include any dynamic roles or permissions because everything runs synchronously on the client-side. Use the `hasPermission` APIs to include checks for dynamic roles and permissions. + + +If you need to add more granular permissions to existing roles, or create new ones, use the [`createAccessControl`](https://www.better-auth.com/docs/plugins/organization#custom-permissions) API. + +For further customization—such as dynamic access control, lifecycle hooks, or team management—see the guidance in the [official documentation](https://www.better-auth.com/docs/plugins/organization) and the [web guide](/docs/web/organizations/rbac). + + +--- +url: /docs/mobile/publishing/android +title: Google Play (Android) +description: Learn how to publish your mobile app to the Google Play Store. +--- + +[Google Play](https://play.google.com/) is the primary platform for distributing Android apps to billions of users worldwide. It's a powerful marketplace that allows you to reach a large audience and monetize your app. + +To submit your app to the Play Store, you'll need to follow a series of steps. We'll walk through those steps here. + + + Before you submit, review the publishing [guidelines](/docs/mobile/marketing) and confirm that your app meets Google's policies to avoid common rejections. + + +## Developer account + +A Google Play Developer account is required to submit your app to the Google Play Store. You can sign up on the [Google Play Console](https://play.google.com/console/) and pay the one-time registration fee. + +![Google Play Developer Account](/images/docs/mobile/publishing/android/developer-account.png) + +To publish apps to Google Play, you must verify your identity. See the [official guide](https://support.google.com/googleplay/android-developer/answer/14177239) for more information. Next, you'll need to create a new app in the [Google Play Console](https://play.google.com/apps/publish/) by clicking the *Create app* button. + +## Submission + +After registering your developer account, setting it up, and preparing your app, you're ready to publish it to the Play Store. + +There are multiple ways to submit your app: + +* **Manual submission:** Upload your app bundle directly to the Play Store via the Play Console. +* **Local submission:** Use [EAS CLI](https://github.com/expo/eas-cli) to submit your app. +* **CI/CD submission:** Use ready-to-use GitHub Actions workflow to automatically submit your app. + +**The first submission must be done manually, while subsequent updates can be submitted automatically.** We'll go through each approach in detail below. + +### Manual submission + +This approach is not recommended, as it is more error-prone and time-consuming due to manual steps. However, it's still the **only way to submit your app for the first time**. You can also use this route if you need to upload a build without EAS Submit (for example, during service maintenance) or if you prefer a fully manual flow. + +**Create the app entry in Google Play Console** + +1. Visit [Google Play Console](https://play.google.com/console/) and sign in. Accept any pending agreements if prompted. +2. Click *Create app*, then enter your app name, default language, app type, and pricing (free/paid). Confirm policy declarations. +3. Finish initial setup tasks (App access, Ads, Content rating, Target audience, Data safety, Privacy policy URL). + +**Upload the `.aab` file to a track (internal/closed/open/production)** + +1. The fastest route for a first upload is often *Internal testing*. Go to *Internal testing* → *Releases* (or choose *Closed/Open/Production*), then click *Create new release*. +2. Upload the `.aab` file, add release notes, and review any warnings. +3. Save and continue through the checks until you're ready to submit for review or roll out to [testers](https://play.google.com/console/about/internal-testing/). + +**Verify and submit for review** + +1. Complete Store listing assets and metadata if not already done. +2. Resolve any policy warnings. When ready, start the rollout to request a [review](/docs/mobile/publishing/android#review). + +After your first manual upload is accepted, you can use [Local submission](/docs/mobile/publishing/android#local-submission) or [CI/CD submission](/docs/mobile/publishing/android#cicd-submission-recommended) for subsequent releases. + +For more information, please refer to the guides listed below. + + + + + + + +### Local submission + + + Due to Google Play API limitations, you must upload your app to Google Play **manually at least once** (to any track: internal, closed, open, or production) before automated submissions will work. See the detailed walkthrough in the ["First Android submission" guide](https://github.com/expo/fyi/blob/main/first-android-submission.md). + + +First, you need to **upload and configure a Google Service Account Key with EAS**. This is the required first step to submit your Android app to the Google Play Store. Follow the [guide on uploading a Google Service Account Key for Play Store submissions with EAS](https://github.com/expo/fyi/blob/main/creating-google-service-account.md) for detailed instructions. + +Next, you have to get your app bundle — if you followed the [checklist](/docs/mobile/publishing/checklist), you should have the `.aab` file in your app folder from the [build step](/docs/mobile/publishing/checklist#build-your-app). If you used GitHub Actions to build your app, you can find the results in the `Builds` tab of your [EAS project](https://expo.dev). Download the artifacts and save them on your local machine. + +Then, navigate to your app folder and run the following command to submit your app to the Play Store: + +```bash +eas submit --platform android +``` + +The command will guide you through the submission process. You can also configure the steps of the submission process by adding a submission profile in `eas.json`. + + + If you upload your Google Service Account key to EAS credentials, you do not need to reference a local file path anywhere. + + +To speed up the submission process, you can use the `--auto-submit` flag to automatically submit a build after it is built: + +```bash +eas build --platform android --auto-submit +``` + +This will automatically submit the build with all the required credentials to the Play Store right after it is built. + + + + + + + + + +### CI/CD submission (recommended) + + + Due to Google Play API limitations, you must upload your app to Google Play **manually at least once** (to any track: internal, closed, open, or production) before automated submissions will work. See the detailed walkthrough in the ["First Android submission" guide](https://github.com/expo/fyi/blob/main/first-android-submission.md). + + +TurboStarter comes with a pre-configured GitHub Actions workflow to automatically submit your mobile app to the Play Store. You'll find the workflow in the `.github/workflows/publish-mobile.yml` file. + +To use this workflow, [upload your Google Play Service Account key to EAS](https://github.com/expo/fyi/blob/main/creating-google-service-account.md) and check your Android credentials setup by running: + +```bash +eas credentials --platform android +``` + +This way, you avoid storing the JSON key in your repository or CI/CD provider. + + + This workflow also requires a [personal access token](https://docs.expo.dev/accounts/programmatic-access/#personal-access-tokens) for your Expo account. Add it as `EXPO_TOKEN` in your [GitHub repository secrets](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions), which will allow the `eas submit` command to run. + + +That's it! After completing these steps, [trigger the workflow](https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/manually-running-a-workflow) to submit your new build to the Play Store automatically 🎉 + + + + + + + + + +## Review + +After filling out the information about your item, you're ready to submit it for review. Click on the *Send for review* button and confirm that you want to proceed with the submission: + +![Send for review](/images/docs/mobile/publishing/android/send-for-review.png) + +To control **when** your app is released after review, you can configure [Managed publishing](https://support.google.com/googleplay/android-developer/answer/9859654) in the Google Play Console. + +After submitting your app for review, it will enter Google's review process. The review time may vary depending on your app, and you'll receive a notification when the status updates. For more details, check out the [Google Play Review Process](https://developers.google.com/workspace/marketplace/about-app-review) documentation. + + + If your submission is rejected, you'll receive an email from Google with the rejection reason. You'll need to fix the issues and upload a new version of your app. + + ![Google Play Rejection](/images/docs/mobile/publishing/android/rejection.png) + + Make sure to follow the [guidelines](/docs/mobile/marketing) or check [publishing troubleshooting](/docs/mobile/troubleshooting/publishing) for more info. + + +When your app is approved by Google, you'll be able to publish it on the Play Store. + +![Your update is live email from Google](/images/docs/mobile/publishing/android/update-live.png) + +You can learn more about the review process in the official guides listed below. + + + + + + + + +--- +url: /docs/mobile/publishing/checklist +title: Checklist +description: Let's publish your TurboStarter app to stores! +--- + +When you're ready to publish your TurboStarter app to stores, follow this checklist. + +This process may take a few hours and some trial and error, so buckle up - you're almost there! + + + + ## Create database instance + + **Why it's necessary?** + + A production-ready database instance is essential for storing your application's data securely and reliably in the cloud. [PostgreSQL](https://www.postgresql.org/) is the recommended database for TurboStarter due to its robustness, features, and wide support. + + **How to do it?** + + You have several options for hosting your PostgreSQL database: + + * [Supabase](/docs/mobile/recipes/supabase) - Provides a fully managed Postgres database with additional features + * [Vercel Postgres](https://vercel.com/storage/postgres) - Serverless SQL database optimized for Vercel deployments + * [Neon](https://neon.tech/) - Serverless Postgres with automatic scaling + * [Turso](https://turso.tech/) - Edge database built on libSQL with global replication + * [DigitalOcean](https://www.digitalocean.com/products/managed-databases) - Managed database clusters with automated failover + + Choose a provider based on your needs for: + + * Pricing and budget + * Geographic region availability + * Scaling requirements + * Additional features (backups, monitoring, etc.) + + + + ## Migrate database + + **Why it's necessary?** + + Pushing database migrations ensures that your database schema in the remote database instance is configured to match TurboStarter's requirements. This step is crucial for the application to function correctly. + + **How to do it?** + + You basically have two possibilities of doing a migration: + + + + TurboStarter comes with predefined Github Action to handle database migrations. You can find its definition in the `.github/workflows/publish-db.yml` file. + + What you need to do is to set your `DATABASE_URL` as a [secret for your Github repository](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions). + + Then, you can run the workflow which will publish the database schema to your remote database instance. + + [Check how to run Github Actions workflow.](https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/manually-running-a-workflow) + + + + You can also run your migrations locally, although this is not recommended for production. + + To do so, set the `DATABASE_URL` environment variable to your database URL (that comes from your database provider) in `.env.local` file and run the following command: + + ```bash + pnpm with-env pnpm --filter @turbostarter/db db:migrate + ``` + + This command will run the migrations and apply them to your remote database. + + [Learn more about database migrations.](/docs/web/database/migrations) + + + + + + ## (Optional) Set up Firebase project + + **Why it's necessary?** + + Setting up a Firebase project is optional, and depends on which features your app is using. For example, if you want to use [Analytics](/docs/mobile/analytics/overview) with [Google Analytics](/docs/mobile/analytics/configuration#google-analytics), setting up a Firebase project is required. + + **How to do it?** + + Please refer to the [Firebase project](/docs/mobile/installation/firebase) section on how to set up and configure your Firebase project. + + + + ## Set up web backend API + + **Why it's necessary?** + + Setting up the backend is necessary to have a place to store your data and to have other features work properly (e.g. authentication, billing or storage). + + **How to do it?** + + Please refer to the [web deployment checklist](/docs/web/deployment/checklist) on how to set up and deploy the web app backend to production. + + + + ## Environment variables + + **Why it's necessary?** + + Setting the correct environment variables is essential for the application to function correctly. These variables include API keys, database URLs, and other configuration details required for your app to connect to various services. + + **How to do it?** + + Use our `.env.example` files to get the correct environment variables for your project. Then add them to your project on the [EAS platform](https://docs.expo.dev/eas/environment-variables/) for correct profile and environment: + + ![EAS environment variables](/images/docs/mobile/eas-environment-variables.png) + + Alternatively, you can add them to your `eas.json` file under correct profile. + + ```json title="eas.json" + { + "profiles": { + "base": { + "env": { + "EXPO_PUBLIC_DEFAULT_LOCALE": "en", + "EXPO_PUBLIC_AUTH_PASSWORD": "true", + "EXPO_PUBLIC_AUTH_MAGIC_LINK": "false", + "EXPO_PUBLIC_THEME_MODE": "system", + "EXPO_PUBLIC_THEME_COLOR": "orange" + } + }, + "production": { + "extends": "base", + "autoIncrement": true, + "env": { + "APP_ENV": "production", + "EXPO_PUBLIC_SITE_URL": "https://www.turbostarter.dev", + } + } + } + } + ``` + + + + ## Build your app + + + Building your app requires an EAS account and project. If you don't have one, you can create it by following the steps [here](https://expo.dev/eas). + + + **Why it's necessary?** + + Building your app is necessary to create a standalone application bundle that can be published to the stores. + + **How to do it?** + + You basically have two possibilities to build a bundle for your app: + + + + TurboStarter comes with predefined Github Action to handle building your app on EAS. You can find its definition in the `.github/workflows/publish-mobile.yml` file. + + What you need to do is to set your `EXPO_TOKEN` as a [secret for your Github repository](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions). You can obtain it from your EAS account, in the [Access Tokens](https://expo.dev/settings/access-tokens) section. + + Then, you can run the workflow which will build the app on [EAS platform](https://expo.dev/eas). + + [Check how to run Github Actions workflow.](https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/manually-running-a-workflow) + + + + You can also run your build locally, although this is not recommended for production. + + To do it, you'll need to have [EAS CLI](https://github.com/expo/eas-cli) installed on your machine. You can install it by running the following command: + + ```bash + npm install -g eas-cli + ``` + + Then, run the following command to build your app with the `production` profile: + + ```bash + eas build --profile production --platform all + ``` + + This will build the app for both platforms (iOS and Android) and output the results in your app folder. + + + + + + ## Submit to stores + + **Why it's necessary?** + + Releasing your app to the stores is essential for making it accessible and discoverable by your users. This allows users to find, install, and trust your application through official channels. + + **How to do it?** + + We've prepared dedicated guides for each store that TurboStarter supports out-of-the-box, please refer to the following pages: + + + + + + + + + +That's it! Your app is now live and accessible to your users, good job! 🎉 + + + * Optimize your store listings with compelling descriptions, keywords, screenshots and preview videos + * Remove placeholder content and replace with your final production content + * Update all visual branding including favicon, scheme, splash screen and app icons + + + +--- +url: /docs/mobile/publishing/ios +title: App Store (iOS) +description: Learn how to publish your mobile app to the Apple App Store. +--- + +[Apple App Store](https://www.apple.com/app-store/) is the primary platform for distributing iOS apps, making them available on iPhones, iPads, and other Apple devices to millions of users worldwide. + +To submit your app to the App Store, you'll need to follow a series of steps. We'll walk through those steps here. + + + Before you submit, review the publishing [guidelines](/docs/mobile/marketing) and confirm that your app meets Apple's policies to avoid common rejections. + + +## Developer account + +An Apple Developer account is required to submit your app to the Apple App Store. You can sign up for an Apple Developer account on the [Apple Developer Portal](https://developer.apple.com/account/). + +![Apple Developer Account](/images/docs/mobile/publishing/ios/developer-account.png) + +To submit apps to the App Store, you must also be a member of the Apple Developer Program. You can join the program by paying the annual fee. + +## Submission + +There are two primary ways to submit your iOS app to the App Store: + +* **Manual:** Uploading the build yourself through Apple's tools, such as [Transporter](https://apps.apple.com/app/transporter/id1450874784) or [Xcode](https://developer.apple.com/xcode/). +* **Automatic (recommended):** Using [EAS Submit](/docs/mobile/publishing/ios#local-submission) or [CI/CD](/docs/mobile/publishing/ios#cicd-submission-recommended), which simplifies the process, ensures consistency, and reduces manual error. + +Below, you'll find guidance for both submission methods—choose the one that fits your workflow and project needs. + +### Manual submission + +This approach is not recommended, as it is more error-prone and time-consuming due to manual steps. Use this route if you need to upload a build without EAS Submit (for example, during service maintenance) or prefer a fully manual flow from macOS. + +**Create the app entry in App Store Connect** + +1. Visit [App Store Connect](https://appstoreconnect.apple.com/) and sign in. Accept any pending agreements if prompted. +2. From Apps, click the + button and select *New App*. +3. Enter the app name, primary language, bundle identifier, and a unique SKU (for example, your bundle ID, such as `com.company.myapp`). +4. Press Create to finish setting up the app record. + +**Upload the IPA with Transporter** + +1. Install [Apple's Transporter](https://apps.apple.com/app/transporter/id1450874784) from the Mac App Store. +2. Open Transporter and sign in with your Apple ID. +3. Drag the `.ipa` into Transporter (or click *Add App* to choose the file). +4. Press *Deliver* to upload. Transfer time varies by file size and network. + +**Verify processing and select the build** + +1. Once uploaded, Apple processes the binary (often 10-20 minutes). +2. Back in [App Store Connect](https://appstoreconnect.apple.com/), open My Apps and select your app. +3. Under the *App Store* tab, select the new build in the *Build* section. If it's missing, wait and refresh. +4. Proceed with the usual App Store steps (screenshots, metadata, compliance, then submit for review). + +For more information about the required metadata, refer to the official guides. + + + + + + + +### Local submission + +If you followed the [checklist](/docs/mobile/publishing/checklist), you should have the `.ipa` file in your app folder from the [build step](/docs/mobile/publishing/checklist#build-your-app). If you used GitHub Actions to build your app, you can find the results in the `Builds` tab of your [EAS project](https://expo.dev). Download the artifacts and save them on your local machine. + +Then, navigate to your app folder and run the following command to submit your app to the App Store: + +```bash +eas submit --platform ios +``` + +The command will guide you through the submission process. You can configure the submission process by adding a submission profile in `eas.json`: + +```json title="eas.json" +{ + "submit": { + "production": { + "ios": { + "ascAppId": "your-app-store-connect-app-id" + } + } + } +} +``` + + + + 1. Sign in to [App Store Connect](https://appstoreconnect.apple.com/) and choose your team. + 2. Open the [Apps](https://appstoreconnect.apple.com/apps) area. + 3. Select your app from the list. + 4. Switch to the *App Store* tab. + 5. Go to *General* → *App Information*. + 6. In *General Information*, the value labeled *Apple ID* is your `ascAppId`. + + ![App Store Connect App Information](/images/docs/mobile/publishing/ios/asc-app-id.png) + + + +To speed up the submission process, you can use the `--auto-submit` flag to automatically submit a build after it is built: + +```bash +eas build --platform ios --auto-submit +``` + +This will automatically submit the build with all the required credentials to the App Store right after it is built. + + + + + + + +### CI/CD submission (recommended) + +TurboStarter comes with a pre-configured GitHub Actions workflow to submit your mobile app to the App Store automatically. It's located in the `.github/workflows/publish-mobile.yml` file. + +To be able to use this workflow, you'd need to fulfill the following prerequisites: + +1. **Configure your App Store Connect API Key** + + Run the following command to configure your App Store Connect API Key: + + ```bash + eas credentials --platform ios + ``` + + The command will prompt you to configure credentials: + + 1. Choose the `production` build profile. + 2. Authenticate with your Apple Developer account and proceed through the prompts. + 3. Pick **App Store Connect → Manage your API Key**. + 4. Enable **Use an API Key for EAS Submit** for the project. + +2. **Provide a submission profile in `eas.json`** + + Next, add a submission profile in `eas.json` with the following: + + ```json title="eas.json" + { + "submit": { + "production": { + "ios": { + "ascAppId": "your-app-store-connect-app-id" + } + } + } + } + ``` + + + + 1) Log into [App Store Connect](https://appstoreconnect.apple.com/) under the correct team. + 2) Go to [Apps](https://appstoreconnect.apple.com/apps) and open your app. + 3) Ensure the *App Store* tab is selected. + 4) Navigate to *General* → *App Information*. + 5) Copy the value shown as *Apple ID* — that is the `ascAppId`. + + ![App Store Connect App Information](/images/docs/mobile/publishing/ios/asc-app-id.png) + + + + + This workflow also requires a [personal access token](https://docs.expo.dev/accounts/programmatic-access/#personal-access-tokens) for your Expo account. Add it as `EXPO_TOKEN` in your [GitHub repository secrets](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions), which will allow the `eas submit` command to run. + + +That's it! After completing these steps, [trigger the workflow](https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/manually-running-a-workflow) to submit your new build to the App Store automatically 🎉 + + + + + + + +## Review + +After completing your app information, you're ready to submit it for review. Click the *Add for review* button and confirm that you want to proceed with the submission: + +![Confirm submission](/images/docs/mobile/publishing/ios/confirm-submission.png) + +On the *Distribution* tab, you can configure the release process after the review is complete — whether you want to release the app automatically after review, later, or manually. + +![App Store Connect Version Release](/images/docs/mobile/publishing/ios/version-release.png) + +Once you've submitted your app for review, it will go through Apple's review process. The duration can vary based on the specifics of your app and you'll be notified when the status changes. For more information, refer to the [App Review](https://developer.apple.com/distribute/app-review/) docs. + + + If your submission is rejected, you'll receive an email from Apple with the rejection reason. You'll need to fix the issues and upload a new version of your app. + + ![App Store Connect Rejection](/images/docs/mobile/publishing/ios/rejection.png) + + Make sure to follow the [guidelines](/docs/mobile/marketing) or check [publishing troubleshooting](/docs/mobile/troubleshooting/publishing) for more information. + + If you need to clarify anything with Apple, you can reply to the app review request in App Store Connect: + + ![App Store Connect Reply to Review](/images/docs/mobile/publishing/ios/reply-to-review.png) + + This helps you understand the rejection and what you need to change to make your app eligible for distribution. + + +When your app is approved by Apple (by email or push notification), you'll be able to publish it on the App Store. + +![Review notification](/images/docs/mobile/publishing/ios/review-notifications.jpeg) + +You can learn more about the review process in the official guides listed below. + + + + + + + + +--- +url: /docs/mobile/publishing/updates +title: Updates +description: Learn how to update your published app. +--- + +After you publish your app to the stores, you can release updates to provide your users with new features and bug fixes. + +TurboStarter offers two ready-to-use methods for updating your apps; we'll walk through both of them below. + +## Over-the-air (OTA) updates + +[Over-the-air (OTA) updates](https://en.wikipedia.org/wiki/Over-the-air_update) allow you to push updates to your app without requiring users to download a new version from the app store. This powerful feature enables rapid iteration and quick fixes. + +![OTA updates](/images/docs/mobile/ota-updates.png) + +TurboStarter integrates with [EAS Update](https://docs.expo.dev/eas-updates/overview/) to provide you with a seamless experience for managing your app updates. We also shipped a native notification that you can use to notify your users about the new updates available. + +Then, to push your update straight to your users, you'll just need to run single command: + +```bash +eas update --channel [channel-name] --message "[message]" +``` + +The app will automatically download the update in the background and install it when your users are ready. You can also configure the update channel and message to be displayed to your users. + +Feel free to check the [official documentation](https://docs.expo.dev/eas-update/getting-started/) for more information. + + + OTA updates are **only supported for non-native changes**. If you need to update your app with a new native feature (or add a package that uses native dependencies), you'll need to submit a new version to the stores - see below for more details. + + +## Submitting a new version + +The most traditional way to update your app is to submit a new version to the stores. This is the most reliable approach, but it can take some time for the new version to be approved and made available to users. + +To submit a new version, update the version number in both your `package.json` file and your `app.config.ts` file. + +```json +{ + ... + "version": "1.0.0", // [!code --] + "version": "1.0.1", // [!code ++] + ... +} +``` + +Next, follow the exact same steps as [when you initially published your app](/docs/mobile/publishing/checklist). When you submit your app for review, be sure to include release notes for the new version. + + +--- +url: /docs/mobile/push-notifications +title: Push notifications +description: Engage your users with personalized notifications. +--- + + + We are working on push notifications to help you engage your users. Stay tuned for updates. + + [See roadmap](https://github.com/orgs/turbostarter/projects/1) + + + +--- +url: /docs/mobile/recipes/supabase +title: Supabase +description: Learn how to set up Supabase as the database (and optional storage) provider for your TurboStarter project. +--- + +[Supabase](https://supabase.com) is an open-source backend platform built on top of PostgreSQL that provides a managed database, storage, and other features out of the box. + +You can adopt Supabase incrementally - start with just the pieces you need (for example, database only, or database + storage) and add more features over time. There's no requirement to integrate everything at once. + +In this guide, we'll walk you through the process of setting up Supabase as a provider for your TurboStarter project. This could include using it as a [database](https://supabase.com/docs/guides/database), [storage](https://supabase.com/docs/guides/storage), [edge runtime for your API](https://supabase.com/docs/guides/functions) and more. + +## Prerequisites + +Before you start, make sure you have: + +* **TurboStarter project** cloned locally with dependencies installed (you can use our [CLI](/docs/web/cli) to create a new project in seconds) +* **Supabase account** - you can create one at [supabase.com](https://supabase.com/sign-up) +* Basic familiarity with the core database docs: + * [Database overview](/docs/web/database/overview) + * [Migrations](/docs/web/database/migrations) + * [Database client](/docs/web/database/client) + + + + ## Create a new Supabase project + + 1. Go to the [Supabase dashboard](https://supabase.com). + 2. Create a **new project** (choose a strong database password and a region close to your users). + 3. Supabase will automatically provision a **PostgreSQL database** for you. + + ![Create a new Supabase project](/images/docs/web/recipes/supabase/create-project.png) + + Optionally, you can customize the **Security options** by choosing the **Only Connection String** option - it will opt out of autogenerating API for tables inside your database. It's not needed for TurboStarter setup, but of course you can still leverage it for your custom use-cases. + + ![Security options](/images/docs/web/recipes/supabase/security-options.png) + + Once the project is ready, you can fetch the connection string. + + + + ## Get the database connection string + + In the Supabase dashboard: + + 1. Open your project. + 2. Click on the **Connect** button at the top. + 3. Locate the **connection string** for your chosen ORM (it will be under the **ORMs** tab). + + ![Connect application](/images/docs/web/recipes/supabase/connect-app.png) + + Copy this value - you'll use it as your `DATABASE_URL`. + + + In your Supabase connection string, you can see a placeholder like `[YOUR-PASSWORD]`. Make sure to replace this with the actual password you set when creating your Supabase project. + + + + + ## Configure environment variables + + TurboStarter reads database connection settings from the **root** `.env.local` file and uses them inside the `@turbostarter/db` package. + + Create (or update) the `.env.local` file in the **monorepo root**: + + ```dotenv title=".env.local" + DATABASE_URL="postgres://postgres.[YOUR-PROJECT-REF]:[YOUR-PASSWORD]@aws-0-[aws-region].pooler.supabase.com:6543/postgres?pgbouncer=true&connection_limit=1" + ``` + + Replace: + + * `YOUR-PROJECT-REF` with your Supabase project ref + * `YOUR-PASSWORD` with the database password you set when creating the project + * `aws-region` with the region shown in the Supabase connection string + + + These variables are validated in the `@turbostarter/db` package and used to create Drizzle client for your database. + + + For more background on how `DATABASE_URL` is used, see [Database overview](/docs/web/database/overview). + + + + ## Setup your Supabase database + + With `DATABASE_URL` now pointing to Supabase, you can apply the existing TurboStarter schema to your Supabase database. + + From the monorepo root, run: + + ```bash + pnpm with-env pnpm --filter @turbostarter/db db:migrate + ``` + + This will: + + * Use your Supabase `DATABASE_URL` from `.env.local` + * Run all pending SQL migrations from `packages/db/migrations` + * Create the full TurboStarter schema (users, billing, demo tables, etc.) in Supabase + + If you're actively iterating on the schema, you can generate new migrations and apply them as described in [Migrations](/docs/web/database/migrations). + + + After running your migrations, you may want to seed your database with initial data (such as demo users or organizations). You can do this by running the following command: + + ```bash + pnpm with-env pnpm turbo db:seed + ``` + + This will populate your Supabase database with some example data you can use to test your application. + + + + + ## Use Supabase Storage as S3-compatible storage + + TurboStarter's storage layer is designed to work seamlessly with **any S3-compatible provider**. In this section, we'll show how to use [Supabase Storage](/docs/web/storage/overview) as your application's file storage back-end. + + Supabase Storage provides a simple, S3-compatible API and is a great choice if you're already using Supabase for your database. + + ### Create a storage bucket + + 1. In the Supabase dashboard, go to **Storage → Buckets**. + 2. Click **Create bucket** (name it whatever you want, for example `avatars` or `uploads`). + 3. Adjust settings based on your needs (e.g. limit the maximum file size, specify the allowed file types, etc.) + + ![Create a new bucket](/images/docs/web/recipes/supabase/create-bucket.png) + + You can create multiple buckets (for documents, images, videos, etc.) if needed. + + ### Generate S3 access keys in Supabase dashboard + + 1. Go to **Storage → S3 → Access keys**. + 2. Click **New access key**. + 3. Give it a descriptive name and create the key. + 4. Copy the **Access key ID** and **Secret access key** to use in your application. + + ![Generate S3 access keys](/images/docs/web/recipes/supabase/s3-keys.png) + + ### Configure S3 environment variables for Supabase Storage + + In your weba application's `.env.local`, add (or update) the S3 configuration used by TurboStarter's storage layer: + + ```dotenv title=".env.local" + S3_REGION="us-east-1" + S3_BUCKET="avatars" + S3_ENDPOINT="https://[YOUR-PROJECT-REF].supabase.co/storage/v1/s3" + S3_ACCESS_KEY_ID="your-access-key-id" + S3_SECRET_ACCESS_KEY="your-secret-access-key" + ``` + + These variables integrate directly with the storage configuration described in: + + * [Storage overview](/docs/web/storage/overview) + * [Storage configuration](/docs/web/storage/configuration) + + Once set, existing TurboStarter file upload flows (e.g. user avatars, organization logos) will use Supabase Storage via presigned URLs. + + + + ## Run your API on Supabase Edge Functions + + As we're using a [Hono](https://hono.dev) as our API server, you can deploy it as a Supabase Edge Function so it runs close to your users. + + At a high level: + + 1. Install the [Supabase CLI](https://supabase.com/docs/guides/cli) and initialize a Supabase project locally with `supabase init`. + 2. Create a new [Edge Function](https://supabase.com/docs/guides/functions/quickstart) (for example `hono-backend`) with `supabase functions new hono-backend`. + 3. Inside the generated function (for example `supabase/functions/hono-backend/index.ts`), set up a basic Hono app and export it via `Deno.serve(app.fetch)`: + + ```ts + import { Hono } from "jsr:@hono/hono"; + + // change this to your function name + const functionName = "hono-backend"; + const app = new Hono().basePath(`/${functionName}`); + + app.get("/hello", (c) => c.text("Hello from hono-server!")); + + Deno.serve(app.fetch); + ``` + + 4. Run the function locally with `supabase start` and `supabase functions serve --no-verify-jwt`, then call it from your TurboStarter app using the local or deployed function URL. + 5. When you're ready, deploy the function with `supabase functions deploy` (or `supabase functions deploy hono-backend`) and manage it using the Supabase dashboard, as described in the [Supabase Edge Functions docs](https://supabase.com/docs/guides/functions). + + This is entirely optional, but it's a great fit for lightweight APIs, webhooks, and other serverless logic you want to run alongside your Supabase project. + + + + ## Explore additional Supabase features + + Supabase is a full Postgres development platform, so beyond the database and storage pieces wired up above you can gradually add more features as your app grows ([see the Supabase homepage](https://supabase.com/) for an overview). + + Some features that fit especially well with TurboStarter's design are: + + * [Realtime](https://supabase.com/docs/guides/realtime) - built on [Postgres replication](https://www.postgresql.org/docs/current/runtime-config-replication.html), so you can stream changes from your existing TurboStarter tables (inserts, updates, deletes) into live UIs without changing how you manage schema or RLS. You still define tables and policies via `@turbostarter/db`, and opt into Realtime on top. + * [Vector](https://supabase.com/docs/guides/vector) - powered by the [pgvector](https://github.com/pgvector/pgvector) extension and stored in regular Postgres tables, making it easy to integrate semantic search or AI features while keeping everything in the same migrations and Drizzle models you already use in TurboStarter. We're using it extensively in our dedicated [AI Kit](/ai). + * [Cron](https://supabase.com/docs/guides/functions/cron) - enables you to schedule background jobs and periodic tasks with [pg\_cron](https://github.com/citusdata/pg_cron). You can define cron jobs for things like scheduled database cleanups, sending emails, report generation, or any recurring logic, all managed alongside your TurboStarter app with full Postgres integration. + + Because these features are all layered on top of Postgres, you can introduce them incrementally and keep managing everything through your familiar workflow. + + + + ## Start the development server + + With the database and other services configured to use Supabase, you can start TurboStarter as usual from the monorepo root: + + ```bash + pnpm dev + ``` + + TurboStarter will now: + + * Use **Supabase Postgres** as your database through `DATABASE_URL` + * Use **Supabase Storage** as your file storage through the S3-compatible endpoint + * Leverage **Supabase Edge Functions** (for example, with Hono) for your serverless backend + + + +That's it! You can now start building your application with Supabase as your main provider. Explore the [Supabase documentation](https://supabase.com/docs) for more features and best practices. + + +--- +url: /docs/mobile/stack +title: Tech Stack +description: A detailed look at the technical details. +--- + +## Turborepo + +[Turborepo](https://turbo.build/) is a monorepo tool that helps you manage your project's dependencies and scripts. We chose a monorepo setup to make it easier to manage the structure of different features and enable code sharing between different packages. + +} /> + +## React Native + Expo + +[React Native](https://reactnative.dev/) is an open-source mobile application development framework created by Facebook. It is used to develop applications for Android and iOS by enabling developers to use [React](https://react.dev) along with native platform capabilities. + +> It's like Next.js for mobile development. + +[Expo](https://expo.dev/) is a framework and a platform built around React Native. It provides a set of tools and services that help you develop, build, deploy, and quickly iterate on iOS, Android, and web apps from the same JavaScript/TypeScript codebase. It's like Next.js for mobile development. + + + } /> + + } /> + + +## Tailwind CSS + +[Uniwind](https://uniwind.dev/) uses Tailwind CSS as scripting language to create a universal style system for React Native. It allows you to use Tailwind CSS classes in your React Native components, providing a familiar styling experience for web developers. We also use [React Native Reusables](https://github.com/mrzachnugent/react-native-reusables) for our headless components library with support of CLI to generate pre-designed components with a single command. + + + } /> + + } /> + + +## Hono & React Query + +[Hono](https://hono.dev) is a small, simple, and ultrafast web framework for the edge. It provides tools to help you build APIs and web applications faster. It includes an RPC client for making type-safe function calls from the frontend. We use Hono to build our serverless API endpoints. + +To make data fetching and caching from our API easy and reliable, we pair Hono with [React Query](https://tanstack.com/query/latest). It helps manage asynchronous data, caching, and state synchronization between the client and backend, delivering a fast and seamless UX. + + + } /> + + + } + /> + + +## Better Auth + +[Better Auth](https://www.better-auth.com) is a modern authentication library for fullstack applications. It provides ready-to-use snippets for features like email/password login, magic links, OAuth providers, and more. We use Better Auth to handle all authentication flows in our application. + +} /> + +## Drizzle + +[Drizzle](https://orm.drizzle.team/) is a super fast [ORM](https://orm.drizzle.team/docs/overview) (Object-Relational Mapping) tool for databases. It helps manage databases, generate TypeScript types from your schema, and run queries in a fully type-safe way. + +We use [PostgreSQL](https://www.postgresql.org) as our default database, but thanks to Drizzle's flexibility, you can easily switch to MySQL, SQLite or any [other supported database](https://orm.drizzle.team/docs/connect-overview) by updating a few configuration lines. + + + } /> + + } /> + + +## EAS (Expo Application Services) + +[EAS](https://expo.dev/eas) is a set of cloud services provided by Expo for React Native app development. It includes tools for building, submitting, and updating your app, as well as over-the-air updates and analytics. + +} /> + + +--- +url: /docs/mobile/tests/e2e +title: E2E tests +description: Simulate real user scenarios across the entire stack with automated end-to-end test tools and examples. +--- + + + End-to-end (E2E) tests will be available soon, allowing you to automate testing of real user flows and interactions across your application. + + Stay tuned for updates as we roll out robust E2E testing resources and examples. + + [See roadmap](https://github.com/orgs/turbostarter/projects/1) + + + +--- +url: /docs/mobile/tests/unit +title: Unit tests +description: Write and run fast unit tests for individual functions and components with instant feedback. +--- + +Unit tests are a type of automated test where individual units or components are tested. The "unit" in "unit test" refers to the smallest testable parts of an application. These tests are designed to verify that each unit of code performs as expected. + +TurboStarter uses [Vitest](https://vitest.dev) as the unit testing framework. It's a blazing-fast test runner built on top of [Vite](https://vitejs.dev), designed for modern JavaScript and TypeScript projects. + + + If you've used [Jest](https://jestjs.io) before, you already know Vitest - it shares the same API. But Vitest is built for speed: native TypeScript support without transpilation, parallel test execution, and a smart watch mode that only re-runs tests affected by your changes. + + It comes with everything you need out of the box - code coverage, snapshot testing, mocking, and a slick UI for debugging. Fast feedback, zero configuration. + + +## Why write unit tests? + +Unit tests give you **fast, focused feedback** on small pieces of your code - individual functions, hooks, or components. Instead of debugging an entire page or flow, you can verify just the logic you care about in isolation. + +They also act as **living documentation**: a good test tells you how a function is supposed to behave, which edge cases are important, and what assumptions the code makes. This makes it much easier to safely refactor or extend features later. + +In TurboStarter, unit tests are designed to be **cheap and quick to run**, so you can keep Vitest running in watch mode while you code. Every change you make is immediately checked, helping you catch regressions before they ever reach integration or end‑to‑end tests. + +## Configuration + +TurboStarter configures Vitest to be **as simple as possible**, while still taking advantage of [Turborepo's caching](https://turborepo.com/docs/crafting-your-repository/caching) and Vitest's [Test Projects](https://vitest.dev/guide/projects). + +```ts title="vitest.config.ts" +import { mergeConfig } from "vitest/config"; + +import baseConfig from "@turbostarter/vitest-config/base"; + +export default mergeConfig(baseConfig, { + test: { + /* your extended test configuration here */ + }, +}); +``` + +* **Per-package tests**: each package that has unit tests defines its own `test` script. This keeps the configuration close to the code and makes it easy to add tests to any workspace. +* **Turbo tasks for CI**: the root `test` task (`pnpm test`) uses `turbo run test` to execute all package-level test scripts with smart caching, which is ideal for CI pipelines where you want to avoid re-running unchanged tests. +* **Vitest Test Projects for local dev**: a root Vitest configuration uses [Test Projects](https://vitest.dev/guide/projects) to run all unit test suites from a single command, which is perfect for local development when you want fast feedback across the whole monorepo. + +This **hybrid setup** combines Turborepo and Vitest Projects in a way that fits TurboStarter's principles: cached, package-aware runs in CI, and a single, unified Vitest entry point for local development. + +You can read more about this setup in the official documentation guides listed below. + + + + + + + +## Running tests + +There are a few different ways to run unit tests, depending on what you're doing: + +* **CI / full test run** - at the root of the repo: + +```bash +pnpm test +``` + +This runs `turbo run test`, which executes all `test` scripts in packages that define them, with Turborepo handling caching so unchanged packages are skipped. This is what you should use in your CI/CD pipeline. + +* **One-off local run with Vitest Projects**: + +```bash +pnpm test:projects +``` + +This uses Vitest [Test Projects](https://vitest.dev/guide/projects) to run all configured unit test suites from a single command, which is great when you want to quickly validate the whole monorepo locally. + +* **Watch mode during development**: + +```bash +pnpm test:projects:watch +``` + +This starts Vitest in watch mode across all Test Projects. As you edit files, only the affected tests are re-run, giving you fast feedback while you work. + +## Code coverage + +Unit test coverage helps you understand **how much** of your code is being tested. While it can't guarantee bug-free code, it shines a light on untested paths that could hide issues or regressions. + +To generate a code coverage report for all unit tests, run: + +```bash +pnpm turbo test:coverage +``` + +This command runs the coverage task across all relevant packages (using Turborepo) and collects the results into a single coverage output. + +To open the coverage report in your browser: + +```bash +pnpm turbo test:coverage:view +``` + +This will build the HTML report and launch it using your default browser, so you can explore which files and branches are covered. + + + You can also store the generated coverage report as a [GitHub Actions artifact](https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts) during your CI/CD pipeline, just add the following steps to your workflow job: + + ```yaml title=".github/workflows/ci.yml" + # your workflow job configuration here + + - name: 📊 Generate coverage + run: pnpm turbo test:coverage + + - name: 🗃️ Archive coverage report + uses: actions/upload-artifact@v5 + with: + name: coverage-${{ github.sha }} + path: tooling/vitest/coverage/report + ``` + + This will generate a test coverage report and upload it as an artifact, so you can access it from GitHub Actions tab for later inspection. + + +A high coverage percentage means your tests execute most lines and branches - but the quality and relevance of your tests matter more than the raw number. Use coverage reports to spot gaps and guide improvements, not as the sole metric of test health. + +![Code coverage](/images/docs/code-coverage.png) + +## Best practices + +Unit tests should work **for you**, not the other way around. Focus on writing tests that make it easier to change code with confidence, not on satisfying arbitrary rules or reaching a magic number in a dashboard. + +Code coverage is a **useful metric**, but it **SHOULD NOT** be the goal. It's better to have a smaller set of high‑value tests that cover critical paths and edge cases than a huge suite of fragile tests that are hard to maintain. + +When in doubt, ask: *“Does this test give **me** confidence that I can change this code without breaking users?”* If the answer is no, refactor or remove it. + +Finally, keep unit tests focused on **small, isolated pieces of logic**. More advanced flows — like multi-step user journeys, cross-service interactions, or full-page behavior — are better covered by [end-to-end (E2E) tests](/docs/web/tests/e2e), where you can verify the system as a whole. + + +--- +url: /docs/mobile/troubleshooting/installation +title: Installation +description: Find answers to common mobile installation issues. +--- + +## Cannot clone the repository + +Issues related to cloning the repository are usually related to a Git misconfiguration in your local machine. The commands displayed in this guide using SSH: these will work only if you have setup your SSH keys in Github. + +If you run into issues, [please make sure you follow this guide to set up your SSH key in Github.](https://docs.github.com/en/authentication/connecting-to-github-with-ssh) + +If this also fails, please use HTTPS instead. You will be able to see the commands in the repository's Github page under the "Clone" dropdown. + +Please also make sure that the account that accepted the invite to TurboStarter, and the locally connected account are the same. + +## Local database doesn't start + +If you cannot run the local database container, it's likely you have not started [Docker](https://docs.docker.com/get-docker/) locally. Our local database requires Docker to be installed and running. + +Please make sure you have installed Docker (or compatible software such as [Colima](https://github.com/abiosoft/colima), [Orbstack](https://github.com/orbstack/orbstack)) and that is running on your local machine. + +Also, make sure that you have enough [memory and CPU allocated](https://docs.docker.com/engine/containers/resource_constraints/) to your Docker instance. + +## I don't see my translations + +If you don't see your translations appearing in the application, there are a few common causes: + +1. Check that your translation `.json` files are properly formatted and located in the correct directory +2. Verify that the language codes in your configuration match your translation files +3. Enable debug mode (`debug: true`) in your i18next configuration to see detailed logs + +[Read more about configuration for translations](/docs/mobile/internationalization#configuration) + +## Expo cannot detect XCode + +If you get the following error: + +```bash +Expo cannot detect Xcode Xcode must be fully installed before you can continue +``` + +This is usually related to the Xcode CLI not being installed. You can fix this by running the following command: + +```bash +sudo xcode-select -s /Applications/Xcode.app/Contents/Developer +``` + +If you still face the issue, please make sure you have the latest version of Xcode installed. + +## "Module not found" error + +This issue is mostly related to either dependency installed in the wrong package or issues with the file system. + +The most common cause is incorrect dependency installation. Here's how to fix it: + +1. Clean the workspace: + + ```bash + pnpm clean + ``` + +2. Reinstall the dependencies: + ```bash + pnpm i + ``` + +If you're adding new dependencies, make sure to install them in the correct package: + +```bash +# For main app dependencies +pnpm install --filter mobile my-package + +# For a specific package +pnpm install --filter @turbostarter/ui my-package +``` + +If the issue persists, please check the file system for any issues. + +### Windows OneDrive + +OneDrive can cause file system issues with Node.js projects due to its file syncing behavior. If you're using Windows with OneDrive, you have two options to resolve this: + +1. Move your project to a location outside of OneDrive-synced folders (recommended) +2. Disable OneDrive sync specifically for your development folder + +This prevents file watching and symlink issues that can occur when OneDrive tries to sync Node.js project files. + + +--- +url: /docs/mobile/troubleshooting/publishing +title: Publishing +description: Find answers to common mobile publishing issues. +--- + +## My app submission was rejected + +If your app submission was rejected, you probably got an email with the reason. You'll need to fix the issues and upload a new build of your app to the store and send it for review again. + +Make sure to follow the [guidelines](/docs/mobile/marketing) when submitting your app to ensure that everything is setup correctly. + +## App Store screenshots don't match requirements + +If your app submission was rejected due to screenshot issues, make sure: + +1. Screenshots match the required dimensions for each device +2. Screenshots accurately represent your app's functionality +3. You have provided screenshots for all required device sizes +4. Screenshots don't contain device frames unless they match Apple's requirements + +[See Apple's screenshot specifications](https://developer.apple.com/help/app-store-connect/reference/screenshot-specifications/) + +## Version number conflicts + +If you get version number conflicts when submitting: + +1. Ensure your `app.json` version matches what's in the store +2. Increment the version number appropriately: + ```bash + "version": "1.0.1", + "android.versionCode": 2, + "ios.buildNumber": "2" + ``` +3. Make sure both stores have unique version numbers + +## Missing or incorrect environment variables + +If your build succeeds but the binary is misconfigured (e.g., API URL shows as `undefined`, Sentry auth fails, or `app.config.*` settings don’t apply), verify your EAS environment variables: + +1. Define variables on EAS and assign them to the correct environment (`development`, `preview`, `production`). +2. For values used in app code, prefix with `EXPO_PUBLIC_` and read via `process.env.EXPO_PUBLIC_...`. +3. For config-time values (bundle identifiers, file paths), read `process.env.VARNAME` from your `app.config.*`. +4. Explicitly set `environment` in `eas.json` build profiles, or pass `--environment` to `eas update` so updates use the same variables as builds. +5. For local development, pull variables into a `.env` file: + ```bash + eas env:pull --environment development + ``` +6. Use secret file variables (e.g., `GOOGLE_SERVICES_JSON`) and reference them in `app.config.*`. +7. Keep `.env` out of git; cloud builds don’t rely on your local `.env`. + +See: [Environment variables in EAS](https://docs.expo.dev/eas/environment-variables/). + +## My app crashes on production build + +If the app works in development but crashes in a production build, check these common causes: + +1. **Missing or incorrect environment variables at build time**. EAS cloud jobs don’t use your local `.env` by default. Ensure variables exist on EAS, are assigned to the correct environment, and use `EXPO_PUBLIC_` for values read in app code. See: [Environment variables in EAS](https://docs.expo.dev/eas/environment-variables/). +2. **Missing native config files**. Provide `google-services.json` / `GoogleService-Info.plist` via secret file variables (e.g., `GOOGLE_SERVICES_JSON`) and reference them in `app.config.*`. +3. **Production-only code paths**. Guard dev-only code with `__DEV__`, avoid importing dev tools in production, and ensure feature flags don’t access undefined values. +4. **Misconfigured native modules or plugins**. Verify required plugins/babel config are present and rebuild after cache clears. + +Try this: + +1. Run the app with a production JS bundle locally to surface minification issues: + ```bash + npx expo start --no-dev --minify + ``` +2. Inspect device logs when the crash occurs (Android: `adb logcat`, iOS: Console.app or Xcode Devices). +3. Rebuild with a clean cache if needed: + ```bash + eas build --clear-cache + ``` + + +--- +url: /docs/web/admin/overview +title: Overview +description: Get started with the admin dashboard in TurboStarter. +--- + +TurboStarter ships with a fully functional admin dashboard - it's a comprehensive tool for managing your application and users from one central place. + +The panel is designed to be intuitive and easy to use, while being customizable and scalable at the same time. You can access it at [/admin](http://localhost:3000/admin). + +![Admin Dashboard](/images/docs/web/admin/home.png) + +## Roles and permissions + +With the initial configuration, your app has two roles available to users: `user` and `admin`. By default, all users are created with the `user` role. + +To access the admin dashboard, a user must have the `admin` permission. + +```ts +const UserRole = { + USER: "user", + ADMIN: "admin", +} as const; +``` + +You can, of course, define more roles and assign granular permissions, but we recommend keeping the number of roles to a minimum. + +## Making a user an admin + +To promote a user to the admin role, use your database provider's UI or leverage our built-in [Studio](/docs/web/database/overview#studio). After you find the user you want to promote, change their role from `user` to `admin`. + +**Ensure the user you are promoting truly requires admin privileges, as they will gain access to all resources and permissions.** + + + To determine whether a user is eligible for the `admin` role, review the following recommendations before promoting the user: + + * The user's email is verified + * Two-factor authentication (2FA) is enabled + * The user is **not** banned or reported + + + + By default, when you [run services](/docs/web/installation/commands#setting-up-services) for the first time, your database is [seeded](/docs/web/installation/commands#seeding-database) with example data. This includes an admin user with test credentials that you can use to test admin functionality locally. + + ```json + { + "email": "me+admin@turbostarter.dev", + "password": "Pa$$w0rd" + } + ``` + + You can modify these by setting the `SEED_EMAIL` and `SEED_PASSWORD` environment variables in the `.env.local` file and running the seed process again. + + **This flow is for local testing purposes only. Do not use it in production.** + + +## Dashboard + +The admin dashboard is your **central place** to manage your application. It includes management tools for each resource you have defined. + +Users with the `admin` permission will see an additional dropdown item in the navigation menu, allowing them to access the admin dashboard. + +![Admin access through the navigation menu](/images/docs/web/admin/user-navigation.png) + +Explore each section of the page below to familiarize yourself with the available tools and options. + + +--- +url: /docs/web/admin/ui +title: Super Admin UI +description: Get familiar with the Super Admin dashboard and start managing your application. +--- + +When you open [/admin](http://localhost:3000/admin), you will see the homepage of the admin dashboard. It includes some quick actions and a summary of the resources you have in your application. Feel free to customize it to your needs. + +To simplify navigation, we also shipped a sidebar that you can use to navigate between different sections and access all admin capabilities. + +![Super Admin UI](/images/docs/web/admin/home.png) + +Check below for more details about each section. + +## Users + +Central place to manage your users. You can see the list of users, search and filter them e.g. by role, 2FA, banned state, and created date. + +Use it to quickly find users that you need to manage or to see how your SaaS is performing. + +![Users](/images/docs/web/admin/users.png) + +When you click on a user, you will see the user details. You can edit the user's name and role, view the user's 2FA status, and see the user's created/updated timestamps. + +You can also see and manage the resources related to this specific user like user's connected accounts/providers, subscriptions, memberships, etc. + +![User](/images/docs/web/admin/user.png) + +Beyond simply viewing user information, the admin dashboard enables you to perform a variety of essential user management actions, including: + +* **Impersonate the user**: Temporarily log in as the selected user to troubleshoot their experience, verify permissions, or offer assistance directly from their perspective. +* **Ban or unban the user**: Restrict access to your application by banning users who violate terms of service, or lift restrictions when appropriate by unbanning them. +* **Delete the user**: Permanently remove a user and any associated data from your system when necessary, such as for GDPR compliance or at user request. + +These administrative actions help you maintain a secure, compliant, and user-friendly environment for your SaaS platform. + +## Organizations + +See how your multi-tenancy is performing in an elegant way presented as a data table. You can search and filter organizations by name, slug, member count and many more. + +![Organizations](/images/docs/web/admin/organizations.png) + +In the single organization view, you can get an overview of the specified organization, e.g see its members or invitations that are associated with it. + +![Organization](/images/docs/web/admin/organization.png) + +Here are some example actions you can perform when managing an organization: + +* **Edit organization details**: Change the organization name, slug, or other profile information. +* **Invite or remove members**: Add new users to an organization or revoke access from existing members. +* **Change member roles**: Promote a member to an admin or downgrade their access. +* **View and manage invitations**: See pending invites and revoke them if needed. +* **Delete organization**: Remove an organization and all its related data (action usually restricted to super admins). +* **Impersonate organization admin**: Temporarily assume the perspective of an organization's admin for troubleshooting. +* **Audit activity**: View a history of actions taken within the organization for security and compliance. + +These actions help you maintain control over multi-tenant environments and ensure that your SaaS remains secure and organized. + +## Customers + +Manage your customers and their subscriptions. Use search, filters, and sorting to quickly find the right record and understand billing state at a glance. + +![Customers](/images/docs/web/admin/customers.png) + +A few example actions you can perform when managing a customer: + +* **Open a customer** to view subscription details and billing history. +* **Change subscription plan** or move a customer to a different tier. +* **Start or extend a trial**, or **cancel a subscription** when needed. +* **Update billing details** like billing email and tax information. +* **Delete customer** to remove them and their billing profile (restricted action). + +## Add your own resources + +It’s your admin panel at the end of the day - extend it with any domain‑specific resources your product needs. The UI ships with reusable table, filter, form, and layout primitives so you can compose new sections quickly. + +To make CRUD panels fast to build, we also provide dedicated hooks, UI components, and API helpers that handle the boring plumbing - data fetching, pagination, sorting, filters, and mutations — so you can focus on your domain logic instead of boilerplate. + + + + ### Start from an example + + Duplicate an existing resource (like `Users` or `Organizations`) as a baseline and adjust the schema/columns to your needs. + + + + ### Build the list view + + Compose a data table with columns, sorting, full‑text search, and filters using the shipped primitives. + + Leverage the dedicated hooks, UI components, and API helpers to handle fetching, pagination, sorting, filters, and mutations with minimal boilerplate. + + + + ### Add a details view + + Create a single‑resource page and, if helpful, add tabs for related entities (e.g., memberships, invoices) using the same building blocks. + + + + ### Wire up navigation + + Register your route in the admin sidebar so the new resource appears alongside the built‑ins. + + + + ### Secure with permissions + + Protect access using your RBAC rules and feature flags to control who can view or manage the resource. + + + +Et voilà! You now have a new resource in your admin panel 🥳 + + +--- +url: /docs/web/ai/configuration +title: Configuration +description: Configure AI integration in your TurboStarter project. +--- + +To ensure scalability and avoid security vulnerabilities, AI requests are proxied by our [Hono backend](/docs/web/api/overview). This means you need to set up AI integration on both the client and server side. + + + We want to avoid exposing API keys directly to the browser, as this could lead to abuse of your key and generate unnecessary costs. + + +In this section, we'll explore the configuration for both sides to give you a smooth start. + +## Server-side + +On the backend, you need to set up two things: environment variables to configure the provider and the procedure to pass responses to the client. Let's go through it! + +### Environment variables + +You need to set the environment variables that correspond to the AI provider you want to use. + +For example, for the OpenAI provider, you would need to set the following environment variables: + +```dotenv +OPENAI_API_KEY= +``` + +However, if you want to use the Anthropic provider, you would need to set these environment variables: + +```dotenv +ANTHROPIC_API_KEY= +``` + +You can find the list of all available providers in the [official documentation](https://sdk.vercel.ai/providers/ai-sdk-providers), along with the required variables that need to be set to ensure the integration works correctly. + +### API endpoint + +As we're proxying the requests, we need to register an [API endpoint](/docs/web/api/new-endpoint) that will be used to pass the responses to the client. + +The steps will be the same as we described in the [API](/docs/web/api/new-endpoint) section. An example implementation could look like this: + +```ts title="ai/router.ts" +export const aiRouter = new Hono().post("/chat", async (c) => + streamText({ + model: openai.responses("gpt-5"), + messages: convertToModelMessages((await c.req.json()).messages), + }).toUIMessageStreamResponse(), +); +``` + +As you can see, we're defining which provider and specific model we want to use here. + +We're also using [Streams API](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Concepts), which allows us to pass the result to the user as soon as the model starts generating it, without needing to wait for the full response to be completed. This gives the user a sense of immediacy and makes the conversation more interactive. + +## Client-side + +To consume the server response, we can leverage the ready-to-use hooks provided by the [Vercel AI SDK](https://sdk.vercel.ai/docs/ai-sdk-ui/chatbot), such as the `useChat` hook: + +```tsx title="page.tsx" +import { useChat } from "@ai-sdk/react"; +import { DefaultChatTransport } from "ai"; + +const AI = () => { + const { messages } = useChat({ + transport: new DefaultChatTransport({ + api: "/api/ai/chat", + }), + }); + + return ( +
+ {messages.map((message) => ( +
+ {message.parts.map((part, i) => { + switch (part.type) { + case "text": + return
{part.text}
; + } + })} +
+ ))} +
+ ); +}; + +export default AI; +``` + +By leveraging this integration, we can easily manage the state of the AI request and update the UI as soon as the response is ready. + +TurboStarter ships with a ready-to-use implementation of AI chat, allowing you to see this solution in action. Feel free to reuse or modify it according to your needs. + + +--- +url: /docs/web/ai/overview +title: Overview +description: Get started with AI integration in your TurboStarter project. +--- + +For AI integration, TurboStarter leverages the [Vercel AI SDK](https://sdk.vercel.ai/docs/introduction), which provides a comprehensive set of tools and utilities to help you build AI applications more easily and efficiently. + + + It's a simple yet powerful library that exposes a unified API for all major AI providers. + + This allows you to build your AI application without worrying about the intricacies of each underlying provider's API. + + +You can learn more about the `ai` package in the [official documentation](https://sdk.vercel.ai/docs/introduction). + +## Features + +The starter comes with the most common AI features built-in, such as: + +* **Chat**: Build chat applications with ease. +* **Streaming responses**: Stream responses from your AI provider in real-time. +* **Image generation**: Generate images using AI technology. +* **Embeddings**: Generate embeddings for your data. +* **Vector stores**: Store and query your embeddings efficiently. + +You can easily compose your application using these building blocks or extend them to suit your specific needs. + +## Providers + +**TurboStarter relies on the AI SDK to provide support for various AI providers.** + +This means you can easily switch between different AI providers without changing your code, as long as they are supported by the `ai` package. + +You can find the list of supported providers in the [official documentation](https://sdk.vercel.ai/providers/ai-sdk-providers). + + + There is also the possibility to add your own custom provider. It just needs to implement the common interface and provide all the necessary methods. + + Read more about this in the [official guide](https://sdk.vercel.ai/providers/community-providers/custom-providers). + + +The configuration for each provider is straightforward and simple. We'll explore this in more detail in the [Configuration](/docs/web/ai/configuration) section. + + +--- +url: /docs/web/analytics/configuration +title: Configuration +description: Learn how to configure web analytics in TurboStarter. +--- + +The `@turbostarter/analytics-web` package offers a streamlined and flexible approach to tracking events in your TurboStarter web app using various analytics providers. It abstracts the complexities of different analytics services and provides a consistent interface for event tracking. + +In this section, we'll guide you through the configuration process for each supported provider. + +Note that the configuration is validated against a schema, so you'll see error messages in the console if anything is misconfigured. + +## Providers + +TurboStarter supports multiple analytics providers, each with its own unique configuration. Below, you'll find detailed information on how to set up and use each supported provider. Choose the one that best suits your needs and follow the instructions in the respective accordion section. + + + + To use Vercel Analytics as your provider, you need to [create a Vercel account](https://vercel.com/) and [set up a project](https://vercel.com/docs/projects/overview). + + Next, enable analytics in your Vercel project settings: + + 1. Navigate to the [Vercel dashboard](https://vercel.com/dashboard). + 2. Select your project. + 3. Go to the *Analytics* section. + 4. Click *Enable* in the dialog. + + + Enabling Web Analytics will add new routes (scoped at `/_vercel/insights/*`) after your next deployment. + + + Also, make sure to activate the Vercel provider as your analytics provider by updating the exports in: + + + + ```ts + // [!code word:vercel] + export * from "./vercel"; + ``` + + + + ```ts + // [!code word:vercel] + export * from "./vercel/server"; + ``` + + + + ```ts + // [!code word:vercel] + export * from "./vercel/env"; + ``` + + + + To customize the provider, you can find its definition in `packages/analytics/web/src/providers/vercel` directory. + + For more information, please refer to the [Vercel Analytics documentation](https://vercel.com/docs/analytics/overview). + + ![Vercel Analytics dashboard](/images/docs/web/analytics/vercel.avif) + + + + To use Google Analytics as your analytics provider, you need to [create a Google Analytics account](https://analytics.google.com/) and [set up a property](https://support.google.com/analytics/answer/9304153). + + Next, add a data stream in your Google Analytics account settings: + + 1. Navigate to [Google Analytics](https://analytics.google.com/). + 2. In the *Admin* section, under *Data collection and modification*, click on *Data Streams*. + 3. Click *Add stream*. + 4. Select *Web* as the platform. + 5. Enter the required details for the stream (at minimum, provide a name and website URL). + 6. Click *Create stream*. + + After creating the stream, you'll need two pieces of information: + + 1. Your [Measurement ID](https://support.google.com/analytics/answer/12270356) (it should look like `G-XXXXXXXXXX`): + + ![Google Analytics Measurement ID](/images/docs/web/analytics/google/id.png) + + 2. Your [Measurement Protocol API secret](https://support.google.com/analytics/answer/9814495): + + ![Google Analytics Measurement Protocol API secret](/images/docs/web/analytics/google/api-secret.png) + + Set these values in your `.env.local` file in the `apps/web` directory and in your deployment environment: + + ```dotenv + NEXT_PUBLIC_ANALYTICS_GOOGLE_MEASUREMENT_ID="your-measurement-id" + GOOGLE_ANALYTICS_SECRET="your-measurement-protocol-api-secret" + ``` + + Also, make sure to activate the Google Analytics provider as your analytics provider by updating the exports in: + + + + ```ts + // [!code word:google-analytics] + export * from "./google-analytics"; + ``` + + + + ```ts + // [!code word:google-analytics] + export * from "./google-analytics/server"; + ``` + + + + ```ts + // [!code word:google-analytics] + export * from "./google-analytics/env"; + ``` + + + + To customize the provider, you can find its definition in `packages/analytics/web/src/providers/google-analytics` directory. + + For more information, please refer to the [Google Analytics documentation](https://developers.google.com/analytics). + + ![Google Analytics dashboard](/images/docs/web/analytics/google/dashboard.jpg) + + + + + PostHog is also one of pre-configured providers for [monitoring](/docs/web/monitoring/overview) in TurboStarter. You can learn more about it [here](/docs/web/monitoring/posthog). + + + To use PostHog as your analytics provider, you need to configure a PostHog instance. You can obtain the [Cloud](https://app.posthog.com/signup) instance by [creating an account](https://app.posthog.com/signup) or [self-host](https://posthog.com/docs/self-host) it. + + Then, create a project and, based on your [project settings](https://app.posthog.com/project/settings), fill the following environment variables in your `.env.local` file in `apps/web` directory and your deployment environment: + + ```dotenv + NEXT_PUBLIC_POSTHOG_KEY="your-posthog-api-key" + NEXT_PUBLIC_POSTHOG_HOST="your-posthog-instance-host" + ``` + + Also, make sure to activate the PostHog provider as your analytics provider by updating the exports in: + + + + ```ts + // [!code word:posthog] + export * from "./posthog"; + ``` + + + + ```ts + // [!code word:posthog] + export * from "./posthog/server"; + ``` + + + + ```ts + // [!code word:posthog] + export * from "./posthog/env"; + ``` + + + + To customize the provider, you can find its definition in `packages/analytics/web/src/providers/posthog` directory. + + For more information, please refer to the [PostHog documentation](https://posthog.com/docs). + + ![PostHog dashboard](/images/docs/web/analytics/posthog.png) + + + + To use Mixpanel as your analytics provider, you need to [create an account](https://mixpanel.com/) and [obtain your project token](https://help.mixpanel.com/hc/en-us/articles/115004502806-Find-Project-Token). + + Then, set it as an environment variable in your `.env.local` file in the `apps/web` directory and your deployment environment: + + ```dotenv + NEXT_PUBLIC_MIXPANEL_TOKEN="your-project-token" + ``` + + Also, make sure to activate the Mixpanel provider as your analytics provider by updating the exports in: + + + + ```ts + // [!code word:mixpanel] + export * from "./mixpanel"; + ``` + + + + ```ts + // [!code word:mixpanel] + export * from "./mixpanel/server"; + ``` + + + + ```ts + // [!code word:mixpanel] + export * from "./mixpanel/env"; + ``` + + + + To customize the provider, you can find its definition in `packages/analytics/web/src/providers/mixpanel` directory. + + For more information, please refer to the [Mixpanel documentation](https://docs.mixpanel.com/). + + ![Mixpanel dashboard](/images/docs/web/analytics/mixpanel.png) + + + + To use Plausible as your analytics provider, you need to [create an account](https://plausible.io/) and [set up a website](https://plausible.io/docs/add-website). + + Then, set your domain and host in your `.env.local` file in the `apps/web` directory and your deployment environment: + + ```dotenv + NEXT_PUBLIC_PLAUSIBLE_DOMAIN="your-website-domain.com" + NEXT_PUBLIC_PLAUSIBLE_HOST="https://plausible.io" + ``` + + Also, make sure to activate the Plausible provider as your analytics provider by updating the exports in: + + + + ```ts + // [!code word:plausible] + export * from "./plausible"; + ``` + + + + ```ts + // [!code word:plausible] + export * from "./plausible/server"; + ``` + + + + ```ts + // [!code word:plausible] + export * from "./plausible/env"; + ``` + + + + To customize the provider, you can find its definition in `packages/analytics/web/src/providers/plausible` directory. + + For more information, please refer to the [Plausible documentation](https://plausible.io/docs). + + ![Plausible dashboard](/images/docs/web/analytics/plausible.png) + + + + To use Umami as your analytics provider, you need to [set up Umami](https://umami.is/docs/getting-started) either by using their [cloud service](https://cloud.umami.is/) or [self-hosting](https://umami.is/docs/install). + + Then, set your website ID and host in your `.env.local` file in the `apps/web` directory and your deployment environment: + + ```dotenv + NEXT_PUBLIC_UMAMI_WEBSITE_ID="your-website-id" + NEXT_PUBLIC_UMAMI_HOST="https://your-umami-instance.com" + UMAMI_API_HOST="https://your-umami-instance.com" + UMAMI_API_KEY="your-api-key" + ``` + + Also, make sure to activate the Umami provider as your analytics provider by updating the exports in: + + + + ```ts + // [!code word:umami] + export * from "./umami"; + ``` + + + + ```ts + // [!code word:umami] + export * from "./umami/server"; + ``` + + + + ```ts + // [!code word:umami] + export * from "./umami/env"; + ``` + + + + To customize the provider, you can find its definition in `packages/analytics/web/src/providers/umami` directory. + + For more information, please refer to the [Umami documentation](https://umami.is/docs). + + ![Umami dashboard](/images/docs/web/analytics/umami.jpg) + + + + To use Open Panel as your analytics provider, you need to [create an account](https://openpanel.dev/) and [set up a client for your project](https://docs.openpanel.dev/docs). + + Then, you would need to set your client ID and secret in your `.env.local` file in `apps/web` directory and your deployment environment: + + ```dotenv + NEXT_PUBLIC_OPEN_PANEL_CLIENT_ID="your-client-id" + OPEN_PANEL_CLIENT_SECRET="your-client-secret" + ``` + + Also, make sure to activate the Open Panel provider as your analytics provider by updating the exports in: + + + + ```ts + // [!code word:open-panel] + export * from "./open-panel"; + ``` + + + + ```ts + // [!code word:open-panel] + export * from "./open-panel/server"; + ``` + + + + ```ts + // [!code word:open-panel] + export * from "./open-panel/env"; + ``` + + + + To customize the provider, you can find its definition in `packages/analytics/web/src/providers/open-panel` directory. + + For more information, please refer to the [Open Panel documentation](https://docs.openpanel.dev/). + + ![Open Panel dashboard](/images/docs/web/analytics/open-panel.webp) + + + + To use Vemetric as your analytics provider, you need to [create an account](https://vemetric.com/) and [obtain your project token](https://vemetric.com/docs/). + + Then, set it as an environment variable in your `.env.local` file in the `apps/web` directory and your deployment environment: + + ```dotenv + NEXT_PUBLIC_VEMETRIC_PROJECT_TOKEN="your-project-token" + ``` + + Also, make sure to activate the Vemetric provider as your analytics provider by updating the exports in: + + + + ```ts + // [!code word:vemetric] + export * from "./vemetric"; + ``` + + + + ```ts + // [!code word:vemetric] + export * from "./vemetric/server"; + ``` + + + + ```ts + // [!code word:vemetric] + export * from "./vemetric/env"; + ``` + + + + To customize the provider, you can find its definition in `packages/analytics/web/src/providers/vemetric` directory. + + For more information, please refer to the [Vemetric documentation](https://vemetric.com/docs/). + + ![Vemetric dashboard](/images/docs/web/analytics/vemetric.webp) + + + +## Client-side context + +To enable tracking events, capturing page views and other analytics features **on the client-side**, you need to wrap your app with the `Provider` component that's implemented by every provider and available through the `@turbostarter/analytics-web` package: + +```tsx title="providers.tsx" +// [!code word:AnalyticsProvider] +import { memo } from "react"; + +import { Provider as AnalyticsProvider } from "@turbostarter/analytics-web"; + +interface ProvidersProps { + readonly children: React.ReactNode; +} + +export const Providers = memo(({ children }) => { + return ( + + {children} + + ); +}); + +Providers.displayName = "Providers"; +``` + +By implementing this setup, you ensure that all analytics events are properly tracked from your client-side code. This configuration allows you to safely utilize the [Analytics API](/docs/web/analytics/tracking) within your client components, enabling comprehensive event tracking and data collection. + + +--- +url: /docs/web/analytics/overview +title: Overview +description: Get started with web analytics in TurboStarter. +--- + +TurboStarter comes with built-in analytics support for multiple providers as well as a unified API for tracking events. This API enables you to easily and consistently track user behavior and app usage across your SaaS application. + +## Providers + +The starter implements multiple providers for managing analytics. To learn more about each provider and how to configure them, see their respective sections: + + + + + + + + + + + + + + + + + + + +All configuration and setup is built-in with a unified API, allowing you to switch between providers by simply changing the exports. You can even introduce your own provider without breaking any tracking-related logic. + +In the following sections, we'll cover how to set up each provider and how to track events in your application. + + +--- +url: /docs/web/analytics/tracking +title: Tracking events +description: Learn how to track events in your TurboStarter web app. +--- + +The implementation strategy for each analytics provider varies depending on whether it's designed for client-side or server-side use. We'll explore both approaches, as they are crucial for ensuring accurate and comprehensive analytics data in your web SaaS application. + +## Client-side tracking + +The client strategy for tracking events, which every provider must implement, is straightforward: + +```ts +export type AllowedPropertyValues = string | number | boolean; + +type TrackFunction = ( + event: string, + data?: Record, +) => void; + +export interface AnalyticsProviderClientStrategy { + Provider: ({ children }: { children: React.ReactNode }) => React.ReactNode; + track: TrackFunction; +} +``` + + + You don't need to worry much about this implementation, as all the providers are already configured for you. However, it's useful to be aware of this structure if you plan to add your own custom provider. + + +As shown above, each provider must supply two key elements: + +1. `Provider` - a component that [wraps your app](/docs/web/analytics/configuration#client-side-context). +2. `track` - a function responsible for sending event data to the provider. + +To track an event, you simply need to invoke the `track` method, passing the event name and an optional data object: + +```tsx +import { track } from "@turbostarter/analytics-web"; + +export const MyComponent = () => { + return ( + + ); +}; +``` + +## Identifying users + +Linking events to specific users enables you to build a full picture of how they're using your product across different sessions, devices, and platforms. + +For identification purposes, the client strategy can also expose `identify` and `reset` methods. They are optional and only needed if you want to identify users in your app and associate their actions with a specific user ID. + +Not all analytics providers support user identification (for example, [Vercel Analytics](/docs/web/analytics/configuration#vercel) and [Plausible](/docs/web/analytics/configuration#plausible)), so make sure your chosen provider exposes these methods before using them. + +```ts +type IdentifyFunction = ( + userId: string, + traits?: Record, +) => void; + +export interface AnalyticsProviderClientStrategy { + identify: IdentifyFunction; + reset: () => void; +} +``` + +To identify users on the client, call the `identify` function, passing the user's ID and an optional traits object: + +```tsx +import { identify } from "@turbostarter/analytics-web"; + +identify("user-123", { name: "John Doe" }); +``` + +This will associate all future events with the user's ID, allowing you to track user behavior and gain valuable insights into your application's usage patterns. + + + The `identify` method is configured out-of-the-box to react to changes in the user's authentication state. + + When the user is authenticated, the `identify` method will be called with the user's ID and traits. When the user is logged out, the `reset` method will be called to clear the existing user identification. + + +## Server-side tracking + +The server strategy for tracking events that every provider has to implement is even simpler: + +```ts +export interface AnalyticsProviderServerStrategy { + track: TrackFunction; +} +``` + + + You don't need to worry much about this implementation, as all the providers are already configured for you. However, it's useful to be aware of this structure if you plan to add your own custom provider. + + +This server-side strategy allows you to track events outside of the browser environment, which is particularly useful for scenarios involving server actions or React Server Components. + +To track an event on the server side, simply call the `track` method, providing the event name and an optional data object: + +```tsx +// [!code word:server] +import { track } from "@turbostarter/analytics-web/server"; + +track("button.click", { + country: "US", + region: "California", +}); +``` + + + Make sure to use the correct import for the `track` function. We're using the same name for both client and server tracking, but they are different functions. For server-side, just add `/server` to the import path (`@turbostarter/analytics-web/server`). + + + + ```tsx + import { track } from "@turbostarter/analytics-web"; + ``` + + + + ```tsx + // [!code word:server] + import { track } from "@turbostarter/analytics-web/server"; + ``` + + + + + + On the server, there are no dedicated identification helpers like `identify` or `reset`. Most providers that support user-level tracking expect you to pass an identifier or traits directly within the `track` call (for example, as a `userId` or similar property), so make sure to check your specific provider's documentation for the recommended way to include user information. + + +Congratulations! You've now mastered event tracking in your TurboStarter web app. With this knowledge, you're well-equipped to analyze user behaviors and gain valuable insights into your application's usage patterns. Happy analyzing! 📊 + + +--- +url: /docs/web/api/client +title: Using API client +description: How to use API client to interact with the API. +--- + +In Next.js, you can access the API client in two ways: + +* **server-side**: in server components and API routes +* **client-side**: in client components + +When you create a new page and want to fetch data, you have flexibility in where to make the API calls. Server Components are great for initial data loading since the fetching happens during server-side rendering, eliminating an extra client-server round trip. The data is then efficiently streamed to the client. + +By default in Next.js, every component is a Server Component. You can opt into client-side rendering by adding the `use client` directive at the top of a component file. Client Components are useful when you need interactive features or want to fetch data based on user interactions. While they're initially server-rendered, they're also hydrated and rendered on the client, allowing you to make API calls directly from the browser. + +Let's explore both approaches to understand their differences and use cases. + +## Server-side + +We're creating a server-side API client inside `apps/web/src/lib/api/server.ts` file. The client automatically handles passing authentication headers from the user's session to secure API endpoints. + +It's pre-configured with all the necessary setup, so you can start using it right away without any additional configuration. + +Then, there is nothing simpler than calling the API from your server component: + +```tsx title="page.tsx" +import { api } from "~/lib/api/server"; + +export default async function MyServerComponent() { + const response = await api.posts.$get(); + const posts = await response.json(); + + /* do something with the data... */ + return
{JSON.stringify(posts)}
; +} +``` + + + +## Client-side + +We're creating a separate client-side API client in `apps/web/src/lib/api/client.tsx` file. It's a simple wrapper around the [@tanstack/react-query](https://tanstack.com/query/latest/docs/framework/react/overview) that fetches or mutates data from the API. + +It also requires wrapping your app in a `QueryClientProvider` component to provide the query client to the rest of the app: + +```tsx title="layout.tsx" +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + {children} + + + ); +} +``` + +Of course, it's all already configured for you, so you just need to start using `api` in your client components: + +```tsx title="page.tsx" +"use client"; + +import { api } from "~/lib/api/client"; + +export default function MyClientComponent() { + const { data: posts, isLoading } = useQuery({ + queryKey: ["posts"], + queryFn: async () => { + const response = await api.posts.$get(); + + if (!response.ok) { + throw new Error("Failed to fetch posts!"); + } + + return response.json(); + }, + }); + + if (isLoading) { + return
Loading...
; + } + + /* do something with the data... */ + return
{JSON.stringify(posts)}
; +} +``` + + + + + Inside the `apps/web/src/lib/api/utils.ts` we're calling a function to get base url of your api, so make sure it's set correctly (especially on production) and your API endpoint is corresponding with the name there. + + ```tsx title="utils.ts" + export const getBaseUrl = () => { + if (typeof window !== "undefined") return window.location.origin; + if (env.NEXT_PUBLIC_URL) return env.NEXT_PUBLIC_URL; + if (env.VERCEL_URL) return `https://${env.VERCEL_URL}`; + return `http://localhost:${process.env.PORT ?? 3000}`; + }; + ``` + + As you can see we're mostly relying on the [environment variables](/docs/web/configuration/environment-variables) to get it, so there shouldn't be any issues with it, but in case, please be aware where to find it 😉 + + +## Handling responses + +As you can see in the examples above, the [Hono RPC](https://hono.dev/docs/guides/rpc) client returns a plain `Response` object, which you can use to get the data or handle errors. However, implementing this handling in every query or mutation can be tedious and will introduce unnecessary boilerplate in your codebase. + +That's why we've developed the `handle` function that unwraps the response for you, handles errors, and returns the data in a consistent format. You can safely use it with any procedure from the API client: + + + + ```tsx + // [!code word:handle] + import { handle } from "@turbostarter/api/utils"; + + import { api } from "~/lib/api/server"; + + export default async function MyServerComponent() { + const posts = await handle(api.posts.$get)(); + + /* do something with the data... */ + return
{JSON.stringify(posts)}
; + } + ``` +
+ + + ```tsx + // [!code word:handle] + + "use client"; + + import { handle } from "@turbostarter/api/utils"; + + import { api } from "~/lib/api/client"; + + export default function MyClientComponent() { + const { data: posts, isLoading } = useQuery({ + queryKey: ["posts"], + queryFn: handle(api.posts.$get), + }); + + if (isLoading) { + return
Loading...
; + } + + /* do something with the data... */ + return
{JSON.stringify(posts)}
; + } + ``` +
+
+ +With this approach, you can focus on the business logic instead of repeatedly writing code to handle API responses in your browser extension components, making your extension's codebase more readable and maintainable. + +The same error handling and response unwrapping benefits apply whether you're building web, mobile, or extension interfaces - allowing you to keep your data fetching logic consistent across all platforms. + + +--- +url: /docs/web/api/internationalization +title: Internationalization +description: Learn how to localize and translate your API. +--- + +Since TurboStarter provides fully featured [internationalization](/docs/web/internationalization/overview) out of the box, you can easily localize not only the frontend but also the API layer. This can be useful when you need to fetch localized data from the database or send emails in different languages. + +Let's explore possibilities of this feature. + +## Request-based localization + +To get the locale for the current request, you can leverage the `localize` middleware: + +```ts title="email/router.ts" +const emailRouter = new Hono().get("/", localize, (c) => { + const locale = c.var.locale; + + // do something with the locale +}); +``` + +Inside it, we're setting the `locale` variable in the current request context, making it available to the procedure. + +## Error handling + +When handling errors in an internationalized API, you'll want to ensure error messages are properly translated for your users. TurboStarter provides built-in support for localizing error messages using error codes and a special `onError` hook. + +That's why it's recommended to use error codes instead of direct messages in your throw statements: + +```ts +throw new HttpException(HttpStatusCode.UNAUTHORIZED, { + code: "auth:error.unauthorized", + /* 👇 optional */ + message: "You are not authorized to access this resource.", +}); +``` + +The error code will then be used to retrieve the localized message, and the returned response from your API will look like this: + +```json +{ + "code": "auth:error.unauthorized", + /* 👇 localized based on request's locale */ + "message": "You are not authorized to access this resource.", + "path": "/api/auth/login", + "status": 401, + "timestamp": "2024-01-01T00:00:00.000Z" +} +``` + +Then, you can either use the returned code to get the localized message in your frontend, or simply use the returned message as is. + + +--- +url: /docs/web/api/mutations +title: Mutations +description: Learn how to mutate data on the server. +--- + +As we saw in [adding new endpoint](/docs/web/api/new-endpoint#maybe-mutation), mutations allow us to modify data on the server, like creating, updating, or deleting resources. They can be defined similarly to queries using our API client. + +Just like queries, mutations can be executed either server-side or client-side depending on your needs. Let's explore both approaches. + +## Server actions + +Next.js provides [server actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations) as a powerful way to handle mutations directly on the server. They're particularly well-suited for form submissions and other data modifications. + +Using our `api` client with server actions is straightforward - you simply call the API function on the server. + +Here's an example of how you can define an action to create a new post: + + + + ```tsx + // [!code word:handle] + "use server"; + + import { revalidatePath } from "next/cache"; + + import { handle } from "@turbostarter/api/utils"; + + import { api } from "~/lib/api/server"; + + export async function createPost(post: PostInput) { + try { + await handle(api.posts.$post)(post); + } catch (error) { + onError(error); + } + + revalidatePath("/posts"); + } + ``` + + + + ```tsx + "use server"; + + import { revalidatePath } from "next/cache"; + + import { api } from "~/lib/api/server"; + + export async function createPost(post: PostInput) { + const response = await api.posts.$post(post); + + if (!response.ok) { + return { error: "Failed to create post" }; + } + + revalidatePath("/posts"); + } + ``` + + + +In the above example we're also using `revalidatePath` to revalidate the path `/posts` to fetch the updated list of posts. + + + + + + + +## useMutation hook + +On the other hand, if you want to perform a mutation on the client-side, you can use the `useMutation` hook that comes straight from the integration with [React Query](https://tanstack.com/query). + + + + ```tsx + // [!code word:handle] + import { handle } from "@turbostarter/api/utils"; + + import { api } from "~/lib/api/react"; + + export function CreatePost() { + const queryClient = useQueryClient(); + const { mutate } = useMutation({ + mutationFn: handle(api.posts.$post), + onSuccess: () => { + toast.success("Post created successfully!"); + queryClient.invalidateQueries({ queryKey: ["posts"] }); + }, + }); + + return
; + } + ``` + + + + ```tsx + import { api } from "~/lib/api/react"; + + export function CreatePost() { + const queryClient = useQueryClient(); + const { mutate } = useMutation({ + mutationFn: async (post: PostInput) => { + const response = await api.posts.$post(post); + + if (!response.ok) { + throw new Error("Failed to create post!"); + } + }, + onSuccess: () => { + toast.success("Post created successfully!"); + queryClient.invalidateQueries({ queryKey: ["posts"] }); + }, + }); + + return ; + } + ``` + + + + + + + + + + +--- +url: /docs/web/api/new-endpoint +title: Adding new endpoint +description: How to add new endpoint to the API. +--- + +To define a new API endpoint, you can either extend an existing entity (e.g. add new customer route) or create a new, separate module. + + + + ## Create new module + + To create a new module you can create a new folder in the `modules` folder. For example `modules/posts`. + + Then you would need to create a router declaration for this module. We're following a convention with the filename describing its purpose, so you would need to create a file named `router.ts` in the `modules/posts` folder. + + ```typescript title="modules/posts/router.ts" + import { Hono } from "hono"; + + import { validate } from "../../middleware"; + + export const postsRouter = new Hono().get( + "/", + validate("query", filtersSchema), + (c) => getAllPosts(c.req.valid("query")), + ); + ``` + + As you can see we're implementing a `.get` method without any additional middlewares for the router. This is a simple way to define a new GET endpoint. + + Also, we're using a [zod](https://zod.dev/) validator to ensure that input passed to the endpoint is correct. + + + + ### Maybe mutation? + + The same way you can define a mutation for the new entity, just by changing the `get` to `post`: + + ```ts title="modules/posts/router.ts" + // [!code word:.post] + export const postsRouter = new Hono().post( + "/", + enforceAuth, + validate("json", postSchema), + (c) => createPost(c.req.valid("json")), + ); + ``` + + Hono supports all [HTTP methods](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods), so you can define a new endpoint for any method you need (e.g. `put`, `delete`, etc.). + + The `enforceAuth` middleware ensures that only authenticated users can access the endpoint, while the zod validator checks if the input data matches the expected schema. This combination provides both authentication and data validation in a single, clean setup. + + [Read more about protected routes](/docs/web/api/protected-routes). + + + + ## Implement logic + + Then you would need to create a controller for this module. There is a place, where the logic happens, e.g. for the `GET /` endpoint we would need to create a `getAllPosts` function which will fetch posts from the database. + + ```typescript title="modules/posts/queries.ts" + import { db } from "@turbostarter/db/server"; + import { posts } from "@turbostarter/db/schema"; + + export const getAllPosts = (filters: Filters) => { + return db.select().from(posts).all().where(/* your filter logic here */); + }; + ``` + + + + ## Register router + + To make the module and its endpoints available in the API you need to register a router for this module in the `index.ts` file: + + ```ts title="index.ts" + import { postsRouter } from "./modules/posts/router"; + + const appRouter = new Hono() + .basePath("/api") + .route("/posts", postsRouter) + /* other routers from your app logic */ + .onError(onError); + + type AppRouter = typeof appRouter; + + export type { AppRouter }; + export { appRouter }; + ``` + + The `basePath` method sets a prefix for all routes in this router. While optional, using it helps organize API endpoints. This modular approach makes the API structure clearer and easier to maintain. + + + +That's it! You've just created a new API endpoint - it's now available at `/api/posts` 🎉 + + + By exporting the `AppRouter` type you get fully type-safe RPC calls in the + client. It's important because without producing a huge amount of code, we're + fully type-safe from the frontend code. It helps avoid passing incorrect data + to the procedure and streamline consuming returned types without a need to + define these types by hand. + + + +--- +url: /docs/web/api/overview +title: Overview +description: Get started with the API. +--- + +TurboStarter is designed to be a scalable and production-ready full-stack starter kit. One of its core features is a dedicated and extensible API layer. To enable this in a type-safe manner, we chose [Hono](https://hono.dev) as the API server and client library. + + + Hono is a small, simple, and ultrafast web framework that gives you a way to + define your API endpoints with full type safety. It provides built-in + middleware for common needs like validation, caching, and CORS. + + It also + includes an [RPC client](https://hono.dev/docs/guides/rpc) for making + type-safe function calls from the frontend. Being edge-first, it's optimized + for serverless environments and offers excellent performance. + + +All API endpoints and their resolvers are defined in the `packages/api` package. Here you will find a `modules` folder that contains the different feature modules of the API. Each module has its own folder and exports all its resolvers. + +For each module, we create a separate Hono router and then aggregate all sub-routers into one main router in the `index.ts` file. + +The API is then exposed as a route handler that will be provided as a Next.js API route: + +```ts title="apps/web/src/app/api/[...route]/route.ts" +import { handle } from "hono/vercel"; + +import { appRouter } from "@turbostarter/api"; + +const handler = handle(appRouter); +export { + handler as GET, + handler as POST, + handler as OPTIONS, + handler as PUT, + handler as PATCH, + handler as DELETE, + handler as HEAD, +}; +``` + + + The API is a part of web, serverless Next.js app. It means that you **must** + deploy it to use the API in other apps (e.g. mobile app, browser extension), + even if you don't need web app itself. It's very simple, as you're just + deploying the Next.js app and the API is just a part of it. + + +Learn more about API in the following sections: + + +--- +url: /docs/web/api/protected-routes +title: Protected routes +description: Learn how to protect your API routes. +--- + +Hono has built-in support for [middlewares](https://hono.dev/docs/guides/middleware), which are functions that can be used to modify the context or execute code before or after a route handler is executed. + +That's how we can secure our API endpoints from unauthorized access. Below are some examples of you can leverage middlewares to protect your API routes. + +## Authenticated access + +After validating the user's authentication status, we store their data in the context using [Hono's built-in context](https://hono.dev/docs/api/context). This allows us to access the user's information in subsequent middleware and procedures without having to re-validate the session. + +Here's an example of middleware that validates whether the user is currently logged in and stores their data in the context: + +```ts title="middleware.ts" +export const enforceAuth = createMiddleware<{ + Variables: { + user: User; + }; +}>(async (c, next) => { + const session = await auth.api.getSession({ headers: c.req.raw.headers }); + const user = session?.user ?? null; + + if (!user) { + throw new HTTPException(HttpStatusCode.UNAUTHORIZED, { + message: "You need to be logged in to access this feature!", + }); + } + + c.set("user", user); + await next(); +}); +``` + +Then we can use our defined middleware to protect endpoints by adding it before the route handler: + +```ts title="billing/router.ts" +export const billingRouter = new Hono().get( + "/customer", + enforceAuth, + async (c) => c.json(await getCustomerByUserId(c.var.user.id)), +); +``` + +## Role-based access + +In most cases, you will want to restrict access to certain endpoints based on the user's role. + +You can achieve this by creating a middleware that will check if the user has the required role and then pass the execution to the next middleware or procedure. + +E.g. for admin endpoints we want to ensure that the user has the `admin` role: + +```ts title="middleware.ts" +export const enforceAdmin = createMiddleware<{ + Variables: { + user: User; + }; +}>(async (c, next) => { + const user = c.var.user; + + if (!hasAdminPermission(user)) { + throw new HttpException(HttpStatusCode.FORBIDDEN, { + message: "You need to be an admin to access this feature!", + }); + } + + await next(); +}); +``` + +Then we can use our defined middleware to protect endpoints by adding it before the route handler: + +```ts title="admin/router.ts" +export const adminRouter = new Hono().get( + "/users", + enforceAuth, + enforceAdmin, + (c) => c.json(...), +); +``` + +## Feature-based access + +When developing your API you may want to restrict access to certain features based on the user's current subscription plan. (e.g. only users with "Pro" plan can access teams). + +You can achieve this by creating a middleware that will check if the user has access to the feature and then pass the execution to the next middleware or procedure: + +```ts title="middleware.ts" +export const enforceFeatureAvailable = (feature: Feature) => + createMiddleware<{ + Variables: { + user: User; + }; + }>(async (c, next) => { + const { data: customer } = await getCustomerById(c.var.user.id); + + const hasFeature = isFeatureAvailable(customer, feature); + + if (!hasFeature) { + throw new HTTPException(HttpStatusCode.PAYMENT_REQUIRED, { + message: "Upgrade your plan to access this feature!", + }); + } + + await next(); + }); +``` + +Use it within your procedure the same way as we did with `enforceAuth` middleware: + +```ts title="teams/router.ts" +export const teamsRouter = new Hono().get( + "/", + enforceAuth, + enforceFeatureAvailable(FEATURES.PRO.TEAMS), + (c) => c.json(...), +); +``` + +These are just examples of what you can achieve with Hono middlewares. You can use them to add any kind of logic to your API (e.g. [logging](https://hono.dev/docs/middleware/builtin/logger), [caching](https://hono.dev/docs/middleware/builtin/cache), etc.) + + +--- +url: /docs/web/auth/2fa +title: Two-Factor Authentication (2FA) +description: Add an extra layer of security with two-factor authentication. +--- + +TurboStarter uses [Better Auth's 2FA plugin](https://www.better-auth.com/docs/plugins/2fa) to provide multi-factor authentication (MFA) capabilities. Two-factor authentication adds an extra layer of security by requiring users to provide a second form of verification alongside their password. + +## Available methods + +TurboStarter supports multiple 2FA verification methods through Better Auth: + +* **TOTP (Time-based One-Time Password)** - codes generated by authenticator apps +* **OTP (One-Time Password)** - codes sent via email or SMS +* **Backup codes** - single-use recovery codes for account recovery + +You can use any TOTP-compatible authenticator app, such as: + +* [Google Authenticator](https://support.google.com/accounts/answer/1066447) +* [Authy](https://authy.com/) +* [Microsoft Authenticator](https://www.microsoft.com/en-us/security/mobile-authenticator-app) +* [1Password](https://1password.com/features/authenticator/) +* [Bitwarden](https://bitwarden.com/help/authenticator-keys/) + +## Enabling 2FA + + + + ### Enable in settings + + Users enable two-factor authentication in their account security settings. + + ![Enable 2FA](/images/docs/web/auth/two-factor/enable.png) + + + + ### Setup authenticator + + A QR code is displayed for users to scan with their authenticator app. + + ![Setup authenticator](/images/docs/web/auth/two-factor/authenticator-app.png) + + + + ### Verify setup + + Users enter a verification code from their authenticator to confirm setup. + + + + ### Backup codes + + Users receive single-use backup codes for account recovery. + + ![Backup codes](/images/docs/web/auth/two-factor/backup-codes.png) + + + + + Recovery codes are essential for account recovery if users lose access to + their authenticator device. Make sure to educate users about safely storing + their backup codes. + + +## Using 2FA + + + + ### Sign in normally + + Users enter their email and password or other methods as usual. + + + + ### 2FA prompt + + After successful password verification, users are prompted for their 2FA code. + + ![2FA prompt](/images/docs/web/auth/two-factor/sign-in-prompt.png) + + + + ### Enter verification code + + Users input the 6-digit code from their authenticator app. + + + + ### Access granted + + Upon successful verification, users gain access to their account. + + + +### Trusted devices + +Users can mark devices as trusted during 2FA verification. Trusted devices won't require 2FA verification for 60 days, providing a balance between security and convenience. + +## Configuration + +2FA is configured through Better Auth's plugin system. The plugin handles: + +* Secure secret generation and storage +* QR code generation for authenticator setup +* TOTP code validation +* Backup code generation and management +* Trusted device management + +For detailed implementation instructions, refer to the [Better Auth 2FA documentation](https://www.better-auth.com/docs/plugins/2fa). + + +--- +url: /docs/web/auth/configuration +title: Configuration +description: Configure authentication for your application. +--- + +TurboStarter supports multiple different authentication methods: + +* **Password** - the traditional email/password method +* **Magic Link** - passwordless email link authentication +* **Passkey** - passkeys as an alternative to passwords +* **Anonymous** - guest mode for unauthenticated users +* **OAuth** - OAuth providers, [Apple](https://www.better-auth.com/docs/authentication/apple), [Google](https://www.better-auth.com/docs/authentication/google) and [Github](https://www.better-auth.com/docs/authentication/github) are set up by default + +All authentication methods are enabled by default, but you can easily customize them to your needs. You can enable or disable any method, and configure them according to your requirements. + + + Remember that you can mix and match these methods or add new ones - for + example, you can have both password and magic link authentication enabled at + the same time, giving your users more flexibility in how they authenticate. + + +Authentication configuration can be customized through a simple configuration file. The following sections explain the available options and how to configure each authentication method based on your requirements. + +## API + +The **server-side** authentication configuration is set at `packages/auth/src/server.ts`. It confgures [Better Auth](https://better-auth.com) package to use the correct providers and settings: + +```ts title="server.ts" +export const auth = betterAuth({ + emailAndPassword: { + enabled: true, + requireEmailVerification: true, + sendResetPassword: () => {}, + }, + emailVerification: { + sendOnSignUp: true, + autoSignInAfterVerification: true, + sendVerificationEmail: () => {}, + }, + database: drizzleAdapter(db, { + provider: "pg", + schema, + }), + plugins: [ + magicLink({ + sendMagicLink: () => {}, + }), + passkey(), + anonymous(), + expo(), + nextCookies(), + ], + socialProviders: { + [SocialProvider.APPLE]: { + clientId: env.APPLE_CLIENT_ID, + clientSecret: env.APPLE_CLIENT_SECRET, + appBundleIdentifier: env.APPLE_APP_BUNDLE_IDENTIFIER, + }, + [SocialProvider.GOOGLE]: { + clientId: env.GOOGLE_CLIENT_ID, + clientSecret: env.GOOGLE_CLIENT_SECRET, + }, + [SocialProvider.GITHUB]: { + clientId: env.GITHUB_CLIENT_ID, + clientSecret: env.GITHUB_CLIENT_SECRET, + }, + }, + + /* other configuration options */ +}); +``` + +The configuration is validated against Better Auth's schema at runtime, providing immediate feedback if any settings are incorrect or insecure. This validation ensures your authentication setup remains robust and properly configured. + +All authentication routes and handlers are centralized within the [Hono API](/docs/web/api/overview), giving you a single source of truth and complete control over the authentication flow. This centralization makes it easier to maintain, debug, and customize the authentication process as needed. + +[Read more about it in the official documentation](https://www.better-auth.com/docs/basic-usage). + +## UI + +We have separate configuration that determines what is displayed to your users in the **UI**. It's set at `apps/web/config/auth.ts`. + +The recommendation is to **not update this directly** - instead, please define the environment variables and override the default behavior. + +```ts title="apps/web/config/auth.ts" +import env from "env.config"; + +import { SocialProvider, authConfigSchema } from "@turbostarter/auth"; + +import type { AuthConfig } from "@turbostarter/auth"; + +export const authConfig = authConfigSchema.parse({ + providers: { + password: env.NEXT_PUBLIC_AUTH_PASSWORD, + magicLink: env.NEXT_PUBLIC_AUTH_MAGIC_LINK, + passkey: env.NEXT_PUBLIC_AUTH_PASSKEY, + anonymous: env.NEXT_PUBLIC_AUTH_ANONYMOUS, + oAuth: [SocialProvider.APPLE, SocialProvider.GOOGLE, SocialProvider.GITHUB], + }, +}) satisfies AuthConfig; +``` + +The configuration is also validated using the Zod schema, so if something is off, you'll see the errors. + +For example, if you want to switch from password to magic link, you'd change the following environment variables: + +```dotenv title=".env.local" +NEXT_PUBLIC_AUTH_PASSWORD=false +NEXT_PUBLIC_AUTH_MAGIC_LINK=true +``` + +To display third-party providers in the UI, you need to set the `oAuth` array to include the provider you want to display. The default is Apple, Google and Github: + +```tsx title="apps/web/config/auth.ts" +providers: { + ... + oAuth: [SocialProvider.APPLE, SocialProvider.GOOGLE, SocialProvider.GITHUB], + ... +}, +``` + +## Third party providers + +To enable third-party authentication providers, you'll need to: + +1. Set up an OAuth application in the provider's developer console (like [Apple Developer Portal](https://developer.apple.com/account/), [Google Cloud Console](https://console.cloud.google.com/), [Github Developer Settings](https://github.com/settings/developers) or any other provider you want to use) +2. Configure the corresponding environment variables in your TurboStarter application + +Each OAuth provider requires its own set of credentials and environment variables. Please refer to the [Better Auth documentation](https://better-auth.com/docs/concepts/oauth) for detailed setup instructions for each supported provider. + + + Make sure to set both development and production environment variables + appropriately. Your OAuth provider may require different callback URLs for + each environment. + + + +--- +url: /docs/web/auth/flow +title: User flow +description: Discover the authentication flow in Turbostarter. +--- + +TurboStarter ships with a fully functional authentication system. Most of the views and components are preconfigured and easily customizable to your needs. + +Here you will find a quick walkthrough of the authentication flow. + +## Sign up + +The sign-up page is where users can create an account. They need to provide their email address and password. + +![Sign up](/images/docs/web/auth/sign-up.png) + +Once successful, users are asked to confirm their email address. This is enabled by default - and due to security reasons, it's not possible to disable it. + + + Make sure to configure the [email provider](/docs/web/emails/configuration) together with the [auth hooks](/docs/web/emails/sending#authentication-emails) to be able to send emails from your app. + + +![Confirm email](/images/docs/web/auth/confirm-email.png) + +## Sign in + +The sign-in page is where users can log in to their account. They need to provide their email address and password, use magic link (if enabled) or third-party providers. + +![Sign in](/images/docs/web/auth/sign-in.png) + +## Sign out + +The sign out button is located in the user menu. + +![Sign out](/images/docs/web/auth/sign-out.png) + +## Forgot password + +The forgot password page is where users can reset their password. They need to provide their email address and follow the instructions in the email. + +![Forgot password](/images/docs/web/auth/forgot-password.png) + +The reset password page is where users land from a forgot email. There they can reset their password by providing new password and confirming it. + +![Reset password](/images/docs/web/auth/update-password.png) + +## Two-factor authentication + +Two-factor authentication is a security feature that requires users to provide a code sent to their email or phone number in addition to their password when logging in. + +![Two-factor authentication](/images/docs/web/auth/two-factor/sign-in-prompt.png) + + +--- +url: /docs/web/auth/oauth +title: OAuth +description: Get started with social authentication. +--- + +Better Auth supports over **30** (!) different [OAuth providers](https://www.better-auth.com/docs/concepts/oauth). They can be easily configured and enabled in the kit without any additional configuration needed. + + + TurboStarter provides you with all the configuration required to handle OAuth providers responses from your app: + + * redirects + * middleware + * confirmation API routes + + You just need to configure one of the below providers on their side and set correct credentials as environment variables in your TurboStarter app. + + +![OAuth providers](/images/docs/web/auth/social-providers.png) + +Third Party providers need to be configured, managed and enabled fully on the provider's side. TurboStarter just needs the correct credentials to be set as environment variables in your app and passed to the [authentication API configuration](/docs/web/auth/configuration#api). + +To enable OAuth providers in your TurboStarter app, you need to: + +1. Set up an OAuth application in the provider's developer console (like [Apple Developer Portal](https://developer.apple.com/account/), [Google Cloud Console](https://console.cloud.google.com/), [Github Developer Settings](https://github.com/settings/developers) or any other provider you want to use) +2. Configure the provider's credentials as environment variables in your app. For example, for Google OAuth: + +```dotenv title="apps/web/.env.local" +GOOGLE_CLIENT_ID= +GOOGLE_CLIENT_SECRET= +``` + +Then, pass it to the authentication configuration in `packages/auth/src/server.ts`: + +```ts title="server.ts" +export const auth = betterAuth({ + ... + + socialProviders: { + [SocialProvider.GOOGLE]: { + clientId: env.GOOGLE_CLIENT_ID, + clientSecret: env.GOOGLE_CLIENT_SECRET, + }, + }, + + ... +}); +``` + + + Better Auth provides a [generic OAuth plugin](https://www.better-auth.com/docs/plugins/generic-oauth) that allows you to add any OAuth provider to your app. + + It supports both OAuth 2.0 and OpenID Connect (OIDC) flows, allowing you to easily add social login or custom OAuth authentication to your application. + + + +--- +url: /docs/web/auth/overview +title: Overview +description: Get started with authentication. +--- + +TurboStarter uses [Better Auth](https://better-auth.com) to handle authentication. It's a secure, production-ready authentication solution that integrates seamlessly with many frameworks and provides enterprise-grade security out of the box. + + + One of the core principles of TurboStarter is to do things **as simple as possible**, and to make everything **as performant as possible**. + + Better Auth provides an excellent developer experience with minimal configuration required, while maintaining enterprise-grade security standards. Its framework-agnostic approach and focus on performance makes it the perfect choice for TurboStarter. + + Recently, Better Auth [announced](https://www.better-auth.com/blog/authjs-joins-better-auth) an incorporation of [Auth.js (27k+ stars on Github)](https://authjs.dev/), making it even more powerful and flexible. + + +![Better Auth](/images/docs/better-auth.png) + +You can read more about Better Auth in the [official documentation](https://better-auth.com/docs). + +TurboStarter supports multiple authentication methods: + +* **Password** - the traditional email/password method +* **Magic Link** - magic links +* **Passkey** - passkeys ([WebAuthn](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API)) +* **Anonymous** - allowing users to proceed anonymously +* **OAuth** - OAuth social providers ([Apple](https://www.better-auth.com/docs/authentication/apple), [Google](https://www.better-auth.com/docs/authentication/google) and [Github](https://www.better-auth.com/docs/authentication/github) preconfigured) + +As well as common applications flows, with ready-to-use views and components: + +* **Sign in** - sign in with email/password or OAuth providers +* **Sign up** - sign up with email/password or OAuth providers +* **Sign out** - sign out +* **Password recovery** - forgot and reset password +* **Email verification** - verify email + +You can construct your auth flow like LEGO bricks - plug in needed parts and customize them to your needs. + + +--- +url: /docs/web/background-tasks/overview +title: Overview +description: Learn about background tasks & cron jobs and how they can power your application. +--- + +Background tasks and cron jobs are long-running processes that execute outside of your main application flow, allowing you to handle time-intensive operations and scheduled workflows without blocking user interactions or hitting serverless function timeouts. + + + Background tasks are ideal for operations that take longer than typical serverless function timeouts (10-60 seconds), such as processing large files, sending batch emails, or making multiple API calls. + + Cron jobs are perfect for recurring operations like daily reports, cleanup tasks, or periodic data synchronization. + + +## What are background tasks? + +**Background tasks** are asynchronous processes that run separately from your main application thread. Instead of forcing users to wait for lengthy operations to complete, you can offload these tasks to run in the background while your application remains responsive. + +**Cron jobs** are scheduled background tasks that run automatically at specific times or intervals. They're perfect for maintenance operations, reports, and recurring workflows that need to happen without user intervention. + +Think of background tasks as your application's *worker threads* - they handle the heavy lifting while your main application stays fast and responsive for users. + + + +## Why use background tasks? + + + + Most serverless platforms have strict execution limits: + + * **[Vercel (Hobby)](https://vercel.com/docs/functions/serverless-functions/runtimes#max-duration)**: 300 seconds + * **[Vercel (Pro)](https://vercel.com/docs/functions/serverless-functions/runtimes#max-duration)**: 800 seconds + * **[Vercel (Enterprise)](https://vercel.com/docs/functions/serverless-functions/runtimes#max-duration)**: 800 seconds + * **[AWS Lambda](https://docs.aws.amazon.com/lambda/latest/dg/configuration-timeout.html)**: 900 seconds + * **[Netlify Functions](https://docs.netlify.com/functions/overview/#default-deployment-options)**: 30 seconds + + Background tasks let you bypass these limitations entirely. + + + + Users don't have to wait for long-running processes. They can continue using + your application while tasks complete in the background. + + + + Cron jobs enable hands-off automation of recurring tasks like daily backups, + weekly reports, or monthly user engagement analysis - all running reliably + without manual intervention. + + + + Background tasks can be automatically retried if they fail, ensuring your + critical processes eventually complete successfully. + + + + Your main application servers stay available to handle user requests instead of being tied up with heavy processing tasks. + + + +## Common use cases + +Here are some typical scenarios where background tasks shine: + + + + * **Video transcoding**: Converting uploaded videos to different formats or resolutions + * **Image optimization**: Batch processing user-uploaded images + * **Document parsing**: Extracting text from PDFs or generating thumbnails + + + + * **Database migrations**: Moving or transforming large datasets + * **Report generation**: Creating complex analytics reports + * **Data synchronization**: Syncing data between different systems + + + + * **Email campaigns**: Sending personalized emails to large user lists + * **Notification processing**: Delivering push notifications across multiple platforms + * **SMS campaigns**: Bulk SMS sending with rate limiting + + + + * **Content generation**: Using AI models to generate text, images, or videos + * **Data analysis**: Running machine learning models on large datasets + * **Natural language processing**: Analyzing text content for insights + + + + * **API synchronization**: Syncing data with external services + * **Webhook processing**: Handling incoming webhooks that trigger complex workflows + * **Social media automation**: Posting content across multiple platforms + + + + * **Daily reports**: Generating and emailing daily analytics or performance reports + * **Database maintenance**: Cleaning up old records, optimizing indexes, or running backups + * **User engagement**: Sending weekly newsletters or monthly account summaries + * **System monitoring**: Health checks, performance monitoring, and alert notifications + * **Content management**: Auto-publishing scheduled content or archiving old posts + + + +## When not to use background tasks? + +Background tasks and cron jobs aren't always the right solution. Consider alternatives for: + +* **Real-time operations**: Tasks that users need immediate results from +* **Simple, fast operations**: Tasks that complete in under 5-10 seconds +* **Database queries**: Standard CRUD operations that should remain synchronous +* **User authentication**: Login/logout processes should be immediate + + + Start with synchronous processing for simple tasks and manual processes for infrequent operations. Only move to background tasks when you hit timeout limitations or user experience issues, and only use cron jobs when you need reliable automation. + + +## Getting started + +Ready to add background tasks to your TurboStarter application? Check out our [Trigger.dev integration guide](/docs/web/background-tasks/trigger) or [Upstash QStash integration guide](/docs/web/background-tasks/qstash) to learn how to implement background tasks using one of the most developer-friendly background job frameworks available. + + +--- +url: /docs/web/background-tasks/qstash +title: Upstash QStash +description: Integrate Upstash QStash with your TurboStarter application for serverless-first background task processing. +--- + +[Upstash QStash](https://upstash.com/docs/qstash) is a serverless message queue and task scheduler designed specifically for serverless and edge environments. It uses HTTP endpoints instead of persistent connections, making it perfect for modern web applications. + + + QStash is built for the serverless world - no infrastructure to manage, automatic scaling, and pay-per-use pricing. It delivers messages to your HTTP endpoints with built-in retries, delays, and scheduling capabilities. + + + + + ## Setup + + Visit [Upstash Console](https://console.upstash.com) and create a free account. Create a new QStash project and note down your credentials. + + Add your QStash credentials to your root environment variables: + + ```dotenv title=".env.local" + QSTASH_URL=https://qstash.upstash.io + QSTASH_TOKEN=your_qstash_token_here + QSTASH_CURRENT_SIGNING_KEY=your_current_signing_key_here + QSTASH_NEXT_SIGNING_KEY=your_next_signing_key_here + ``` + + You can find these values in your Upstash Console under the QStash project settings. + + For production, make sure to add these environment variables to your deployment platform. + + + + ## Install dependencies + + Add the QStash SDK to your API package: + + ```bash + pnpm add --filter api @upstash/qstash + ``` + + + + ## Create the QStash client + + Create a utility file to initialize the QStash client in your API package: + + ```ts title="packages/api/src/lib/qstash.ts" + import { Client } from "@upstash/qstash"; + + import { env } from "~/env"; + + export const qstashClient = new Client({ + baseUrl: env.QSTASH_URL, + token: env.QSTASH_TOKEN, + }); + ``` + + + + ## Create task handlers + + QStash delivers messages to HTTP endpoints, so you'll create API routes to handle your background tasks. + + Let's create task handlers for common operations: + + + + ```ts title="packages/api/src/modules/tasks/router.ts" + import { Hono } from "hono"; + import * as z from "zod"; + + import { qstashVerifyMiddleware } from "../../middleware/qstash-verify"; + import { dailyCleanupHandler } from "./handlers/daily-cleanup"; + import { processUserDataHandler } from "./handlers/process-user-data"; + + const processUserDataSchema = z.object({ + userId: z.string(), + operation: z.enum(["export", "analyze", "cleanup"]), + }); + + export const tasksRouter = new Hono() + .basePath("/tasks") + // Apply QStash signature verification to all task routes + .use(qstashVerifyMiddleware) + .post("/process-user-data", processUserDataHandler) + .post("/daily-cleanup", dailyCleanupHandler); + ``` + + + + ```ts title="packages/api/src/modules/tasks/handlers/process-user-data.ts" + import type { Context } from "hono"; + import * as z from "zod"; + + const ProcessUserDataSchema = z.object({ + userId: z.string(), + operation: z.enum(["export", "analyze", "cleanup"]), + }); + + export async function processUserDataHandler(c: Context) { + try { + const payload = ProcessUserDataSchema.parse(await c.req.json()); + const { userId, operation } = payload; + + console.log("Starting user data processing", { userId, operation }); + + switch (operation) { + case "export": + // Simulate data export + await new Promise((resolve) => setTimeout(resolve, 2000)); + console.log("User data exported successfully"); + return c.json({ + success: true, + result: "Data exported to CSV", + }); + + case "analyze": + // Simulate data analysis + await new Promise((resolve) => setTimeout(resolve, 5000)); + console.log("User data analysis completed"); + return c.json({ + success: true, + result: { totalActions: 156, avgSessionTime: "4m 32s" }, + }); + + case "cleanup": + // Simulate data cleanup + await new Promise((resolve) => setTimeout(resolve, 3000)); + console.log("User data cleanup completed"); + return c.json({ + success: true, + result: "Removed 23 obsolete records", + }); + + default: + throw new Error(`Unknown operation: ${operation}`); + } + } catch (error) { + console.error("Task failed:", error); + return c.json({ error: "Task failed" }, 500); + } + } + ``` + + + + ```ts title="packages/api/src/modules/tasks/handlers/daily-cleanup.ts" + import type { Context } from "hono"; + + export async function dailyCleanupHandler(c: Context) { + try { + console.log("Starting daily cleanup"); + + // Cleanup old logs + await new Promise((resolve) => setTimeout(resolve, 5000)); + console.log("Logs cleaned up"); + + // Cleanup temporary files + await new Promise((resolve) => setTimeout(resolve, 3000)); + console.log("Temp files cleaned up"); + + // Generate daily reports + await new Promise((resolve) => setTimeout(resolve, 8000)); + console.log("Reports generated"); + + return c.json({ + success: true, + cleanupTime: new Date().toISOString(), + itemsProcessed: 1247, + }); + } catch (error) { + console.error("Daily cleanup failed:", error); + return c.json({ error: "Daily cleanup failed" }, 500); + } + } + ``` + + + + ```ts title="packages/api/src/middleware/qstash-verify.ts" + import { Receiver } from "@upstash/qstash"; + import { createMiddleware } from "hono/factory"; + + export const qstashVerifyMiddleware = createMiddleware(async (c, next) => { + const currentSigningKey = process.env.QSTASH_CURRENT_SIGNING_KEY; + const nextSigningKey = process.env.QSTASH_NEXT_SIGNING_KEY; + + if (!currentSigningKey || !nextSigningKey) { + return c.json({ error: "QStash signing keys not configured" }, 500); + } + + const signature = c.req.header("upstash-signature"); + + if (!signature) { + return c.json({ error: "Missing QStash signature" }, 401); + } + + try { + const body = await c.req.text(); + + const receiver = new Receiver({ + currentSigningKey, + nextSigningKey, + }); + + const isValid = receiver.verify({ + body, + signature, + }); + + if (!isValid) { + return c.json({ error: "Invalid QStash signature" }, 401); + } + + // Re-create the request with the body for the next handler + const newRequest = new Request(c.req.url, { + method: c.req.method, + headers: c.req.headers, + body, + }); + + c.req = newRequest; + await next(); + } catch (error) { + console.error("QStash signature verification failed:", error); + return c.json({ error: "Invalid signature" }, 401); + } + }); + ``` + + + + + + ## Register task routes + + Add the tasks router to your main API: + + ```ts title="packages/api/src/index.ts" + import { tasksRouter } from "./modules/tasks/router"; + + const appRouter = new Hono() + .basePath("/api") + .route("/tasks", tasksRouter) + // ... other existing routers + .onError(onError); + + export { appRouter }; + ``` + + + + ## Triggering tasks + + You can trigger tasks from your TurboStarter application by publishing messages to QStash, which will then deliver them to your task endpoints. + + Create a service to handle task triggering: + + ```ts title="packages/api/src/modules/tasks/service.ts" + import { qstashClient } from "../../lib/qstash"; + + function getTaskUrl(taskName: string): string { + const baseUrl = process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000"; + return `${baseUrl}/api/tasks/${taskName}`; + } + + export class TaskService { + static async processUserData( + userId: string, + operation: "export" | "analyze" | "cleanup", + ) { + return await qstashClient.publishJSON({ + url: getTaskUrl("process-user-data"), + body: { userId, operation }, + }); + } + + static async scheduleUserDataProcessing( + userId: string, + operation: "export" | "analyze" | "cleanup", + delaySeconds: number, + ) { + return await qstashClient.publishJSON({ + url: getTaskUrl("process-user-data"), + body: { userId, operation }, + delay: `${delaySeconds}s`, + }); + } + + static async scheduleDailyCleanup() { + return await qstashClient.schedules.create({ + destination: getTaskUrl("daily-cleanup"), + cron: "0 2 * * *", // Daily at 2 AM + }); + } + } + ``` + + + + ## Create API endpoints for triggering + + Create endpoints to trigger tasks from your application: + + ```ts title="packages/api/src/modules/tasks/trigger/router.ts" + import { Hono } from "hono"; + import * as z from "zod"; + + import { enforceAuth, validate } from "../../middleware"; + import { TaskService } from "./service"; + + const triggerUserDataSchema = z.object({ + userId: z.string(), + operation: z.enum(["export", "analyze", "cleanup"]), + delaySeconds: z.number().optional(), + }); + + export const taskTriggerRouter = new Hono() + .post( + "/trigger/process-user-data", + enforceAuth, + validate("json", triggerUserDataSchema), + async (c) => { + const { userId, operation, delaySeconds } = c.req.valid("json"); + + const result = delaySeconds + ? await TaskService.scheduleUserDataProcessing( + userId, + operation, + delaySeconds, + ) + : await TaskService.processUserData(userId, operation); + + return c.json({ + success: true, + messageId: result.messageId, + message: delaySeconds + ? `Task scheduled to run in ${delaySeconds} seconds` + : "Task queued for immediate processing", + }); + }, + ) + .post("/trigger/daily-cleanup", enforceAuth, async (c) => { + const result = await TaskService.scheduleDailyCleanup(); + + return c.json({ + success: true, + scheduleId: result.scheduleId, + message: "Daily cleanup scheduled", + }); + }); + ``` + + Add it to your main router: + + ```ts title="packages/api/src/index.ts" + import { taskTriggerRouter } from "./modules/tasks/trigger/router"; + + const appRouter = new Hono() + .basePath("/api") + .route("/tasks", tasksRouter) + .route("/", taskTriggerRouter) // Trigger routes at root level + // ... other existing routers + .onError(onError); + + export { appRouter }; + ``` + + + + ## Using tasks in your application + + ### From the client + + ```tsx title="apps/web/src/modules/tasks/process-data-button.tsx" + "use client"; + + import { handle } from "@turbostarter/api/utils"; + import { useMutation } from "@tanstack/react-query"; + + import { api } from "~/lib/api/client"; + + export function ProcessDataButton({ userId }: { userId: string }) { + const { mutate: processData, isPending } = useMutation({ + mutationFn: handle(api.trigger["process-user-data"].$post), + onSuccess: (data) => { + console.log("Task queued:", data.messageId); + }, + }); + + return ( + + ); + } + ``` + + ### From a server action + + ```ts title="apps/web/src/app/actions/user-actions.ts" + "use server"; + + import { handle } from "@turbostarter/api/utils"; + + import { api } from "~/lib/api/server"; + + export async function processUserData( + userId: string, + operation: "export" | "analyze" | "cleanup", + ) { + try { + const result = await handle(api.trigger["process-user-data"].$post)({ + json: { userId, operation }, + }); + + return { + success: true, + messageId: result.messageId, + }; + } catch (error) { + console.error("Failed to queue background task:", error); + throw new Error("Failed to queue background task"); + } + } + ``` + + + +## Advanced features + +### Cron jobs & scheduling + +QStash makes it easy to schedule recurring tasks: + +```ts +// Schedule a task to run every day at 2 AM +await qstashClient.schedules.create({ + destination: `${baseUrl}/api/tasks/daily-cleanup`, + cron: "0 2 * * *", +}); + +// Schedule a task to run every Monday at 9 AM +await qstashClient.schedules.create({ + destination: `${baseUrl}/api/tasks/weekly-report`, + cron: "0 9 * * 1", +}); + +// One-time delayed task +await qstashClient.publishJSON({ + url: `${baseUrl}/api/tasks/reminder`, + body: { userId: "123", type: "follow-up" }, + delay: "3d", // 3 days from now +}); +``` + +### Topics (Fanout pattern) + +Create topics to send messages to multiple endpoints: + +```ts +// Create a topic +await qstashClient.topics.upsert({ + name: "user-events", + endpoints: [ + { url: `${baseUrl}/api/tasks/update-analytics` }, + { url: `${baseUrl}/api/tasks/send-notification` }, + { url: `${baseUrl}/api/tasks/update-crm` }, + ], +}); + +// Publish to topic - all endpoints will receive the message +await qstashClient.publishJSON({ + topic: "user-events", + body: { + userId: "123", + event: "user-registered", + timestamp: new Date().toISOString(), + }, +}); +``` + +### Queues (Sequential processing) + +Create queues for ordered task processing: + +```ts +// Create a queue +const queue = qstashClient.queue({ queueName: "user-onboarding" }); + +// Add tasks to queue (they'll run in order) +await queue.enqueueJSON({ + url: `${baseUrl}/api/tasks/send-welcome-email`, + body: { userId: "123" }, +}); + +await queue.enqueueJSON({ + url: `${baseUrl}/api/tasks/setup-user-profile`, + body: { userId: "123" }, +}); + +await queue.enqueueJSON({ + url: `${baseUrl}/api/tasks/trigger-onboarding-sequence`, + body: { userId: "123" }, +}); +``` + +## Monitoring and debugging + +### QStash Dashboard + +Visit the [Upstash Console](https://console.upstash.com) to monitor your tasks: + +* **Message tracking**: See all messages, their status, and delivery attempts +* **Logs**: View detailed logs for each message delivery +* **Analytics**: Monitor throughput, success rates, and error patterns +* **Schedules**: Manage and monitor your cron jobs +* **Dead letter queue**: Handle messages that failed after all retries + +### Local development + +During development, you can: + +1. **Use ngrok** for local testing: + + ```bash + # Install ngrok + npm install -g ngrok + + # Expose your local server + ngrok http 3000 + + # Use the ngrok URL in your QStash configuration + ``` + +2. **Check message delivery** in the Upstash Console + +3. **Use console.log** in your task handlers for debugging + +## Best practices + + + + Use the QStash signature verification middleware to ensure messages are authentic: + + ```ts + // ✅ Good - Always verify QStash signatures + .use(qstashVerifyMiddleware) + + // ❌ Not secure - Accepting unverified requests + .post("/tasks/sensitive-operation", handler) + ``` + + + + Return appropriate HTTP status codes so QStash knows whether to retry: + + ```ts + // ✅ Good - Clear error handling + try { + await processTask(payload); + return c.json({ success: true }); + } catch (error) { + console.error("Task failed:", error); + // 5xx = QStash will retry, 4xx = won't retry + return c.json({ error: "Task failed" }, 500); + } + ``` + + + + Make your tasks safe to run multiple times in case of retries: + + ```ts + // ✅ Good - Check if work already done + const existingResult = await db.findProcessedResult(payload.id); + if (existingResult) { + return c.json({ success: true, result: existingResult }); + } + + // Proceed with processing... + ``` + + + + Configure timeouts based on your expected processing time: + + ```ts + // For quick tasks + await qstashClient.publishJSON({ + url: taskUrl, + body: payload, + timeout: "30s", + }); + + // For longer tasks + await qstashClient.publishJSON({ + url: taskUrl, + body: payload, + timeout: "300s", // 5 minutes + }); + ``` + + + + Include relevant context in your logs: + + ```ts + console.log("Task started", { + taskType: "process-user-data", + userId: payload.userId, + operation: payload.operation, + timestamp: new Date().toISOString(), + }); + ``` + + + +## Next steps + +With QStash integrated into your TurboStarter application, you can now: + +* **Process background tasks** without worrying about serverless timeouts +* **Schedule recurring operations** with reliable cron job functionality +* **Handle high-volume messaging** with automatic retries and scaling +* **Build complex workflows** using topics, queues, and delays + +Ready to explore more advanced features? Check out the official documentation for webhooks, batch operations, and advanced routing patterns. + + + + + + + + +--- +url: /docs/web/background-tasks/trigger +title: trigger.dev +description: Integrate trigger.dev with your TurboStarter application for reliable background task processing. +--- + +[trigger.dev](https://trigger.dev) is an open-source background jobs framework that lets you write reliable workflows in plain async code. + + + trigger.dev provides automatic retries, real-time monitoring, and seamless scaling - all while letting you write background tasks in familiar JavaScript/TypeScript code directly in your TurboStarter project. + + + + + ## Setup + + Visit [trigger.dev](https://trigger.dev) and create a free account. Create a new project and note down your API key. + + Add your trigger.dev API key to your root environment variables: + + ```dotenv title=".env.local" + TRIGGER_SECRET_KEY=your_secret_key_here + ``` + + For production, make sure to add the production API key to your deployment environment. + + + + ## Create a new package in your repository + + You can use the [Turbo generator](/docs/web/customization/add-package) to quickly scaffold the package structure: + + ```bash + turbo gen package + ``` + + When prompted, name your package `tasks`. This will create the basic structure for you. + + Alternatively, create a new folder `tasks` in the `/packages` directory and add the following files: + + + + ```json + { + "name": "@turbostarter/tasks", + "private": true, + "version": "0.1.0", + "type": "module", + "exports": { + ".": "./src/index.ts" + }, + "scripts": { + "clean": "git clean -xdf .cache .turbo dist node_modules", + "dev": "pnpm dlx trigger.dev@latest dev", + "deploy": "pnpm dlx trigger.dev@latest deploy", + "format": "prettier --check . --ignore-path ../../.gitignore", + "lint": "eslint", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@trigger.dev/sdk": "3.3.17" + }, + "devDependencies": { + "@trigger.dev/build": "3.3.17", + "@turbostarter/eslint-config": "workspace:*", + "@turbostarter/prettier-config": "workspace:*", + "@turbostarter/tsconfig": "workspace:*", + "eslint": "catalog:", + "prettier": "catalog:", + "typescript": "catalog:" + }, + "prettier": "@turbostarter/prettier-config" + } + ``` + + + + ```json + { + "extends": "@turbostarter/tsconfig/base.json", + "include": ["**/*.ts"], + "exclude": ["dist", "build", "node_modules"] + } + ``` + + + + ```ts + import { defineConfig } from "@trigger.dev/sdk"; + + export default defineConfig({ + project: "your_project_id", // Replace with your actual project ID + runtime: "node", + logLevel: "log", + maxDuration: 300, + dirs: ["./src/trigger"], + }); + ``` + + + + + + ## Create your first task + + Now create your first task in the `packages/tasks/src/trigger` directory: + + + + ```ts title="packages/tasks/src/trigger/process-user-data.ts" + import { task, logger, wait } from "@trigger.dev/sdk"; + import * as z from "zod"; + + const ProcessUserDataSchema = z.object({ + userId: z.string(), + operation: z.enum(["export", "analyze", "cleanup"]), + }); + + export const processUserDataTask = task({ + id: "process-user-data", + run: async (payload: z.infer) => { + const { userId, operation } = payload; + + logger.info("Starting user data processing", { userId, operation }); + + switch (operation) { + case "export": + await wait.for({ seconds: 2 }); + logger.info("User data exported successfully"); + return { success: true, result: "Data exported to CSV" }; + + case "analyze": + await wait.for({ seconds: 5 }); + logger.info("User data analysis completed"); + return { + success: true, + result: { totalActions: 156, avgSessionTime: "4m 32s" }, + }; + + case "cleanup": + await wait.for({ seconds: 3 }); + logger.info("User data cleanup completed"); + return { success: true, result: "Removed 23 obsolete records" }; + + default: + throw new Error(`Unknown operation: ${operation}`); + } + }, + }); + ``` + + + + ```ts title="packages/tasks/src/trigger/daily-cleanup.ts" + import { schedules, task, logger, wait } from "@trigger.dev/sdk"; + + export const dailyCleanupTask = task({ + id: "daily-cleanup", + run: async () => { + logger.info("Starting daily cleanup"); + + // Cleanup old logs + await wait.for({ seconds: 5 }); + logger.info("Logs cleaned up"); + + // Cleanup temporary files + await wait.for({ seconds: 3 }); + logger.info("Temp files cleaned up"); + + // Generate daily reports + await wait.for({ seconds: 8 }); + logger.info("Reports generated"); + + return { + success: true, + cleanupTime: new Date().toISOString(), + itemsProcessed: 1247, + }; + }, + }); + + // Schedule the task to run daily at 2 AM + schedules.create({ + task: "daily-cleanup", + cron: "0 2 * * *", + }); + ``` + + + + ```ts title="packages/tasks/src/index.ts" + export * from "./trigger/process-user-data"; + export * from "./trigger/daily-cleanup"; + ``` + + + + + + ## Test your task + + You can test your tasks locally by running: + + ```bash + # Start the development server + pnpm --filter @turbostarter/tasks dev + ``` + + This will deploy your tasks to trigger.dev in the development environment, allowing you to trigger them from the dashboard or programmatically. + + + + ## Deploy your tasks + + To deploy your tasks to production on trigger.dev, run: + + ```bash + pnpm --filter @turbostarter/tasks deploy + ``` + + You can also add this command as an automated deployment step in your CI/CD pipeline by creating a new GitHub action. + + Add the `TRIGGER_ACCESS_TOKEN` secret to your repository secrets, which you can create in the trigger.dev dashboard. + + ```yml title=".github/workflows/deploy-tasks.yml" + name: Deploy to trigger.dev (prod) + + on: + push: + branches: + - main + + jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: lts/* + - uses: pnpm/action-setup@v4 + - name: Install dependencies + run: pnpm install + - name: Deploy trigger tasks + env: + TRIGGER_ACCESS_TOKEN: ${{ secrets.TRIGGER_ACCESS_TOKEN }} + run: | + pnpm --filter @turbostarter/tasks deploy + ``` + + + + ## Triggering tasks + + You can trigger tasks from your TurboStarter application using the API layer. + + + While you can trigger tasks directly from your frontend or server components using the trigger.dev SDK, it's recommended to use the API layer approach shown below. + + This provides better security, validation, and separation of concerns. + + + First, add the `@turbostarter/tasks` package as a dependency to your API package: + + ```json title="packages/api/package.json" + { + "dependencies": { + "@turbostarter/tasks": "workspace:*" + } + } + ``` + + ### From an API endpoint + + Create a new API module to handle task triggering: + + ```ts title="packages/api/src/modules/tasks/tasks.router.ts" + import { tasks } from "@trigger.dev/sdk"; + import { Hono } from "hono"; + import * as z from "zod"; + import type { processUserDataTask } from "@turbostarter/tasks"; + + import { enforceAuth, validate } from "../../middleware"; + + const processUserDataSchema = z.object({ + userId: z.string(), + operation: z.enum(["export", "analyze", "cleanup"]), + }); + + export const tasksRouter = new Hono().post( + "/process-user-data", + enforceAuth, + validate("json", processUserDataSchema), + async (c) => { + const { userId, operation } = c.req.valid("json"); + + const handle = await tasks.trigger( + "process-user-data", + { userId, operation }, + ); + + return c.json({ + success: true, + taskId: handle.id, + message: "Background task started successfully", + }); + }, + ); + ``` + + Then register it in your main API router: + + ```ts title="packages/api/src/index.ts" + import { tasksRouter } from "./modules/tasks/tasks.router"; + + const appRouter = new Hono() + .basePath("/api") + .route("/tasks", tasksRouter) + // ... other existing routers + .onError(onError); + + export { appRouter }; + ``` + + ### From the client + + You can call the task endpoint from your web app using TurboStarter's API client: + + ```tsx title="apps/web/src/modules/tasks/process-data-button.tsx" + "use client"; + + import { handle } from "@turbostarter/api/utils"; + import { useMutation } from "@tanstack/react-query"; + + import { api } from "~/lib/api/client"; + + export function ProcessDataButton({ userId }: { userId: string }) { + const { mutate: processData, isPending } = useMutation({ + mutationFn: handle(api.tasks["process-user-data"].$post), + onSuccess: (data) => { + console.log("Task started:", data.taskId); + }, + }); + + return ( + + ); + } + ``` + + ### From a server action + + ```ts title="apps/web/src/app/actions/user-actions.ts" + "use server"; + + import { handle } from "@turbostarter/api/utils"; + + import { api } from "~/lib/api/server"; + + export async function processUserData(userId: string, operation: string) { + try { + const result = await handle(api.tasks["process-user-data"].$post)({ + json: { userId, operation }, + }); + + return { + success: true, + taskId: result.taskId, + }; + } catch (error) { + console.error("Failed to trigger background task:", error); + throw new Error("Failed to start background task"); + } + } + ``` + + + +## Monitoring and debugging + +### Dashboard access + +Visit the [trigger.dev dashboard](https://trigger.dev) to monitor your tasks: + +* View task execution logs and performance metrics +* Track success and failure rates across all your tasks +* Monitor task duration and resource usage +* Replay failed tasks with a single click +* Set up alerts for task failures or performance issues + +### Local development + +During development, run your tasks locally while connected to trigger.dev: + +```bash +# Start everything in the workspace +pnpm dev + +# or start the tasks package only +pnpm --filter @turbostarter/tasks dev +``` + +This allows you to: + +* Test tasks locally with real data +* Debug with breakpoints and console logs +* See immediate feedback as you develop + +## Best practices + + + + ```ts + // ✅ Good - Clear and descriptive + id: "user-data-export-csv"; + id: "weekly-newsletter-campaign"; + id: "cleanup-temp-files"; + + // ❌ Not so good - Generic and unclear + id: "task1"; + id: "job"; + id: "process"; + ``` + + + + ```ts + run: async (payload) => { + try { + const result = await processData(payload); + logger.info("Task completed successfully", { result }); + return result; + } catch (error) { + logger.error("Task failed:", error.message); + throw error; // Re-throw to trigger retry logic + } + }, + ``` + + + + ```ts + logger.info("Processing started", { + userId: payload.userId, + operation: payload.operation, + timestamp: new Date().toISOString(), + }); + ``` + + + + Instead of one massive task, create focused, single-purpose tasks that can be composed together for complex workflows. + + + + Set retry policies based on your task's requirements: + + ```ts + // For critical operations + retry: { + maxAttempts: 5, + minTimeoutInMs: 2000, + maxTimeoutInMs: 30000, + factor: 2, + } + + // For less critical operations + retry: { + maxAttempts: 2, + minTimeoutInMs: 1000, + maxTimeoutInMs: 5000, + factor: 1.5, + } + ``` + + + +## Next steps + +With trigger.dev integrated into your TurboStarter application, you can now: + +* **Handle long-running operations** that would timeout in serverless functions +* **Schedule recurring tasks** like reports, cleanups, and maintenance +* **Process background jobs** reliably with automatic retries +* **Scale your application** without worrying about task execution infrastructure + +Ready to explore more advanced features? Check out the official documentation for additional capabilities like webhooks, batching, and custom integrations. + + + + + + + + +--- +url: /docs/web/billing/configuration +title: Configuration +description: Configure billing for your application. +--- + +The billing configuration schema replicates your billing provider's schema, so that: + +* we can display the data in the UI (pricing table, billing section, etc.) +* create the correct checkout session +* make some features work correctly - such as feature-based access + +It is common to all billing providers and placed in `packages/billing/src/config/index.ts`. Some billing providers have some differences in what you can or cannot do. In these cases, the schema will try to validate and enforce the rules - but it's up to you to make sure the data is correct. + +The schema is based on few entities: + +* **Plans:** The main product you are selling (e.g., "Pro Plan", "Starter Plan", etc.) +* **Prices:** The pricing plan for the product (e.g., "Monthly", "Yearly", etc.) +* **Discounts:** The discount for the price (e.g., "10% off", "20% off", etc.) + +```ts title="index.ts" +type BillingConfig = { + plans: PlanWithPrices[]; + discounts: Discount[]; +}; +``` + + + Getting the IDs of your plans is **extremely important** - as these are used to: + + * create the correct checkout + * manage your customers billing data + + Please take it easy while you configure this, do one step at a time, and test it thoroughly. + + +## Billing provider + +To set the billing provider, you need to modify the exports in the `packages/billing/src/providers` directory. It defaults to [Stripe](/docs/web/billing/stripe). + + + + ```ts + // [!code word:stripe] + export * from "./stripe"; + ``` + + + + ```ts + // [!code word:stripe] + export * from "./stripe/env"; + ``` + + + +It's important to set it correctly, as this is used to determine the correct API calls and environment variables used during the communication with the billing provider. + +## Billing model + +To set the billing model, you need to modify the `BILLING_MODEL` environment variable. It defaults to `recurring` as it's the most common model for SaaS apps. + +```dotenv +BILLING_MODEL="recurring" +``` + +This field will be used to display corresponding data in the UI (e.g. in pricing tables) and to create the correct checkout session. + + + For now, TurboStarter supports two billing models: + + * `recurring` - for subscription-based models + * `one-time` - for one-time payments + + When changing it, make sure to also update corresponding data on the provider side to match it with the correct billing model. + + +## Plans + +Plans are the main products you are selling. They are defined by the following fields: + +```ts title="index.ts" +export const config = billingConfigSchema.parse({ + ... + plans: [ + { + id: PricingPlanType.PREMIUM, + name: "Premium", + description: "Become a power user and gain benefits", + badge: "Bestseller", + prices: [], + }, + ], + ... +}) satisfies BillingConfig; +``` + +Let's break down the fields: + +* `id`: The unique identifier for the plan (e.g., `free`, `pro`, `enterprise`, etc.). **This is chosen by you, it doesn't need to be the same one as the one in the provider.** It's also used to determine the access level of the plan. +* `name`: The name of the plan +* `description`: The description of the plan +* `badge`: A badge to display on the product (e.g., "Bestseller", "Popular", etc.) + +The majority of these fields are going to populate the pricing table in the UI. + +### Prices + +Prices are the pricing plans for the plan. They are defined by the following fields: + +```ts title="index.ts" +export const config = billingConfigSchema.parse({ + ... + plans: [ + { + id: PricingPlanType.PREMIUM, + name: "Premium", + description: "Become a power user and gain benefits", + badge: "Bestseller", + prices: [ + { + /* 👇 This is the `priceId` from the provider (e.g. Stripe), `variantId` (e.g. Lemon Squeezy) or `productId` (e.g. Polar) */ + id: "price_1PpZAAFQH4McJDTlig6Fxsyy", + amount: 1900, + currency: "usd", + interval: RecurringInterval.MONTH, + trialDays: 7, + type: BillingModel.RECURRING, + }, + ], + }, + ], + ... +}) satisfies BillingConfig; +``` + +Let's break down the fields: + +* `id`: The unique identifier for the price. **This must match the price ID in the billing provider** +* `amount`: The amount of the price (displayed values will be divided by 100) +* `currency`: The currency of the price (only currencies from the [current locale](/docs/web/internationalization/overview) will be displayed - defaults to `usd`) + + + Make sure to have the same currency set on your third-party billing provider (e.g. as a [store currency](https://docs.lemonsqueezy.com/help/payments/currencies) on Lemon Squeezy) + + +* `interval`: The interval of the price (e.g., `month`, `year`, etc.) +* `trialDays`: The number of trial days for the price +* `type`: The type of the price (e.g., `recurring`, `one-time`, etc.) + +The amount is set for UI purposes. The billing provider will handle the actual billing - therefore, please make sure the amount is correctly set in the billing provider. + + + Make sure to set the correct price ID that corresponds to the price in the billing provider. This is very important - as this is used to identify the correct price when creating a checkout session. + + +### One-off payments + +One-off payments are a type of price that is used to create a checkout session for a one-time payment. They are defined by the following fields: + +```ts title="index.ts" +export const config = billingConfigSchema.parse({ + ... + plans: [ + { + id: PricingPlanType.PREMIUM, + name: "Premium", + description: "Become a power user and gain benefits", + badge: "Bestseller", + prices: [ + { + /* 👇 This is the `priceId` from the provider (e.g. Stripe), `variantId` (e.g. Lemon Squeezy) or `productId` (e.g. Polar) */ + id: "price_1PpUagFQH4McJDTlHCzOmyT6", + amount: 29900, + currency: "usd", + type: BillingModel.ONE_TIME, + }, + ], + }, + ], + ... +}) satisfies BillingConfig; +``` + +Let's break down the fields: + +* `id`: The unique identifier for the price. **This must match the price ID in the billing provider** +* `amount`: The amount of the price (displayed values will be divided by 100) +* `currency`: The currency of the price (only currencies from the [current locale](/docs/web/internationalization/overview) will be displayed - defaults to `usd`) +* `type`: The type of the price (e.g. `recurring`, `one-time`, etc.). In this case it's `one-time` as it's a one-off payment. + +Please remember that the cost is set for UI purposes. **The billing provider will handle the actual billing - therefore, please make sure the cost is correctly set in the billing provider.** + +### Custom prices + +Sometimes - you want to display a price in the pricing table - but not actually have it in the billing provider. This is common for custom plans, free plans that don't require the billing provider subscription, or plans that are not yet available. + +To do so, let's add the `custom` flag to the price: + +```ts title="index.ts" +{ + id: "enterprise-monthly", + label: "Contact us!", + href: "/contact", + interval: RecurringInterval.MONTH, + custom: true, + type: BillingModel.RECURRING, +} +``` + +Here's the full example: + +```ts title="index.ts" +export const config = billingConfigSchema.parse({ + ... + plans: [ + { + id: PricingPlanType.PREMIUM, + name: "Premium", + description: "Become a power user and gain benefits", + badge: "Bestseller", + prices: [ + { + id: "premium-monthly", + label: "Contact us!", + href: "/contact", + interval: RecurringInterval.MONTH, + custom: true, + type: BillingModel.RECURRING, + }, + ], + }, + ], + ... +}) satisfies BillingConfig; +``` + +As you can see, the plan is now a custom plan. The UI will display the plan in the pricing table, but it won't be available for purchase. + +We do this by adding the following fields: + +* `custom`: A flag to indicate that the plan is custom. This will prevent the plan from being available for purchase. It's set to `false` by default. +* `label`: This is used to display the label in the pricing table instead of the price. +* `href`: The link to the page where the user can contact you. This is used in the pricing table. + + + All labels and descriptions can be translated using the [internationalization](/docs/web/internationalization/overview) feature. The UI will display the correct translation based on the user's locale. + + ```ts title="index.ts" + label: "common:contactUs", + ``` + + To make strings translatable, make sure to provide the translation key in the config. + + +### Discounts + +Sometimes, you want to offer a discount to your users. This is done by adding a discount to the price in `discounts` field. + +```ts title="index.ts" +export const config = billingConfigSchema.parse({ + ... + discounts: [ + { + code: "50OFF", + type: BillingDiscountType.PERCENT, + off: 50, + appliesTo: [ + "price_1PpUagFQH4McJDTlHwsCzOmyT6", + ], + }, + ], + ... +}) satisfies BillingConfig; +``` + +Let's break down the fields: + +* `code`: The code of the discount (e.g., "50OFF", "10% off", etc.) **This must match the code configured in the billing provider** +* `type`: The type of the discount (e.g., `percent`, `amount`, etc.) +* `off`: The amount of the discount (e.g., 50 for 50% off) +* `appliesTo`: The list of prices that the discount applies to. This is the price ID that you've configured above for the price. + +This data will allow to display the correct banner in the UI e.g. "10% off for the first 100 customers!" and to apply the discount to the correct price at checkout. + +## Adding more products, plans and discounts + +Simply add more plans, prices and discounts to the arrays. The UI **should** be able to handle it in most traditional cases. If you have a more complex billing schema, you may need to adjust the UI accordingly. + + +--- +url: /docs/web/billing/creem +title: Creem +description: Manage your customers data and subscriptions using Creem. +--- + + + We are working on adding [Creem](https://www.creem.io/) integration to our platform. As soon as it's ready, we will update this page with the necessary information. + + [See roadmap](https://github.com/orgs/turbostarter/projects/1) + + + +--- +url: /docs/web/billing/lemon-squeezy +title: Lemon Squeezy +description: Manage your customers data and subscriptions using Lemon Squeezy. +--- + +[Lemon Squeezy](https://lemonsqueezy.com/) is another billing provider available within TurboStarter. Here we'll go through the configuration and how to set it up as a provider for your app. + +To switch to Lemon Squeezy, you need to update the exports in: + + + + ```ts + // [!code word:lemon-squeezy] + export * from "./lemon-squeezy"; + ``` + + + + ```ts + // [!code word:lemon-squeezy] + export * from "./lemon-squeezy/env"; + ``` + + + +Then, let's configure the integration: + + + + ## Get API keys + + After you have created your account and a store for [Lemon Squeezy](https://lemonsqueezy.com/), you will need to create a new API key. You can do this by going to the [API page](https://app.lemonsqueezy.com/settings/api) in the settings and clicking on the plus button. You will need to give your API key a name and then click on the *Create* button. Once you have created your API key, you will need to copy the API key to use it in the setup of the integration. + + For local development, make sure to use [Test Mode](https://docs.lemonsqueezy.com/help/getting-started/test-mode) to not mess with the real transactions. + + + + ## Set environment variables + + You need to set the following environment variables: + + ```dotenv title="apps/web/.env.local" + LEMONSQUEEZY_API_KEY="" # Your Lemon Squeezy API key + LEMONSQUEEZY_SIGNING_SECRET="" # Your Lemon Squeezy webhook signing secret + LEMONSQUEEZY_STORE_ID="" # Your Lemon Squeezy store ID (can be found under Settings > Stores next to your store url, e.g #12345) + ``` + + **Please do not add the secret keys to the .env file in production.** During development, you can place them in `.env.local` as it's not committed to the repository. In production, you can set them in the environment variables of your hosting provider. + + + + ## Create products + + For your users to choose from the available subscription plans, you need to create those Products first on the [Products page](https://app.lemonsqueezy.com/products). You can create as many products as you want. + + Create one product per plan you want to offer. You can add multiple variant within the product to offer multiple models or different billing intervals. + + ![Lemon Squeezy Products](/images/docs/web/billing/lemon-squeezy/products.webp) + + To offer multiple intervals for each plan, you can use the [Variant](https://docs.lemonsqueezy.com/help/products/variants) feature of Lemon Squeezy. Just create one variant for each interval/model you want to offer. + + ![Lemon Squeezy Variants](/images/docs/web/billing/lemon-squeezy/variants.png) + + + You need to make sure that the price ID you set in the configuration matches the ID of the variant you created in Lemon Squeezy. + + [See configuration](/docs/web/billing/configuration#prices) for more information. + + + + + ## Create a webhook + + To sync the current subscription status or checkout conclusion and other information to your database, you need to set up a webhook. + + The webhook handling code comes ready to use with TurboStarter, you just have to create the webhook in the Lemon Squeezy dashboard and insert the URL for your project. + + To configure a new webhook, go to the [Webhooks page](https://app.lemonsqueezy.com/settings/webhooks) in the Lemon Squeezy settings and click the *Plus* button. + + ![Lemon Squeezy Webhook](/images/docs/web/billing/lemon-squeezy/webhook.png) + + Select the following events: + + * For subscriptions: + * `subscription_created` + * `subscription_updated` + * `subscription_cancelled` + * For one-off payments: + * `order_created` + + You will also have to enter a *Signing secret* which you can get by running the following command in your terminal: + + ```bash + openssl rand -base64 32 + ``` + + Copy the generated string and paste it into the *Signing secret* field. + + You also need to add this secret to your environment variables: + + ```dotenv title="apps/web/.env.local" + LEMONSQUEEZY_WEBHOOK_SECRET=your-signing-secret + ``` + + To get the callback URL for the webhook, you can either use a local development URL or the URL of your deployed app: + + ### Local development + + If you want to test the webhook locally, you can use [ngrok](https://ngrok.com) to create a tunnel to your local machine. Ngrok will then give you a URL that you can use to test the webhook locally. + + To do so, install ngrok and run it with the following command (while your TurboStarter web development server is running): + + ```bash + ngrok http 3000 + ``` + + ![Ngrok](/images/docs/web/billing/stripe/ngrok.png) + + This will give you a URL (see the *Forwarding* output) that you can use to create a webhook in Lemon Squeezy. Just use that url and add `/api/billing/webhook` to it. + + + + ### Production deployment + + When going to production, you will need to set the webhook URL and the events you want to listen to in Lemon Squeezy. + + The webhook path is `/api/billing/webhook`. If your app is hosted at `https://myapp.com` then you need to enter `https://myapp.com/api/billing/webhook` as the URL. + + All the relevant events are automatically handled by TurboStarter, so you don't need to do anything else. If you want to handle more events please check [Webhooks](/docs/web/billing/webhooks) for more information. + + + +## Add discount + +You can add a discount for your customers that will apply on a specific price. + +You can create the discount on [Discounts page](https://app.lemonsqueezy.com/discounts). + +![Lemon Squeezy Discounts](/images/docs/web/billing/lemon-squeezy/discount.png) + +You can set there a details of discount such as products that it should apply to, amount off, duration, max redemptions and more. + + + +You need to add also the discount code and details to TurboStarter billing configuration to enable displaying it in the UI, creating checkout sessions with it and calculate prices. + +[See discounts configuration](/docs/web/billing/configuration#discounts) for more details. + +That's it! 🎉 You have now set up Lemon Squeezy as a billing provider for your app. + +Feel free to add more products, prices, discounts and manage your customers data and subscriptions using Lemon Squeezy. + + + Make sure that the data you set in the configuration matches the details of things you created in Lemon Squeezy. + + [See configuration](/docs/web/billing/configuration) for more information. + + + +--- +url: /docs/web/billing/overview +title: Overview +description: Get started with billing in TurboStarter. +--- + +The `@turbostarter/billing` package is used to manage subscriptions, one-off payments, and more. + +Inside, we're making an abstraction layer that allows us to use different billing providers without breaking our code nor changing the API calls. + +![Billing Providers](/images/docs/billing-providers.webp) + +## Providers + +TurboStarter implements multiple providers for managing billing: + +* [Stripe](/docs/web/billing/stripe) +* [Lemon Squeezy](/docs/web/billing/lemon-squeezy) +* [Polar](/docs/web/billing/polar) +* [Creem](/docs/web/billing/creem) (coming soon) + +All configuration and setup is built-in with a unified API, so you can switch between providers by simply changing the exports and even introduce your own provider without breaking any billing-related logic. + +## Subscriptions vs. One-off payments + +TurboStarter supports both one-off payments and subscriptions. You have the choice to use one or both. What TurboStarter cannot assume with certainty is the billing mode you want to use. By default, we assume you want to use subscriptions, as this is the most common billing mode for SaaS applications. + +This means that - by default - TurboStarter will be looking for a subscription plan when visiting the billing section or pricing page. + +**It's easily customizable** - [take a look at configuration](/docs/web/billing/configuration). + +### But I want to use both + +Perfect - you can, but you need to customize the pages to display the correct data. + +Depending on the service you use, you will need to set the environment variables accordingly. By default - the billing package uses [Stripe](/docs/web/billing/stripe). Alternatively, you can use [Lemon Squeezy](/docs/web/billing/lemon-squeezy) or [Polar](/docs/web/billing/polar). In the future, we will also add [Creem](/docs/web/billing/creem). + + +--- +url: /docs/web/billing/polar +title: Polar +description: Manage your customers data and subscriptions using Polar. +--- + +[Polar](https://www.polar.com/) is another billing provider available within TurboStarter. Here we'll go through the configuration and how to set it up as a provider for your app. + +To switch to Polar, you need to update the exports in: + + + + ```ts + // [!code word:polar] + export * from "./polar"; + ``` + + + + ```ts + // [!code word:polar] + export * from "./polar/env"; + ``` + + + +Then, let's configure the integration: + + + + ## Get the access token + + After you have created your account for [Polar](https://www.polar.com/) and created your store, you will need to get the API key. + + Under the *Settings*, scroll to *Developers* and click "New token". Enter a name for the token, set the expiration duration and select the scopes you want the token to have. + + To keep it simple, you can select all scopes. + + ![Polar Access Token](/images/docs/web/billing/polar/access-token.png) + + For local development, make sure to use [Sandbox Mode](https://docs.polar.sh/integrate/sandbox) to not mess with the real transactions. + + + + ## Set environment variables + + You need to set the following environment variables: + + ```dotenv title="apps/web/.env.local" + POLAR_ACCESS_TOKEN="" # Your Polar access token + POLAR_WEBHOOK_SECRET="" # Your Polar webhook secret + POLAR_ORGANIZATION_SLUG="" # Your Polar organization slug (can be found under Settings > Organization) + ``` + + **Please do not add the secret keys to the .env file in production.** During development, you can place them in `.env.local` as it's not committed to the repository. In production, you can set them in the environment variables of your hosting provider. + + + + ## Create products + + For your users to choose from the available subscription plans, you need to create those Products first on the [Products page](https://docs.polar.sh/features/products). You can create as many products as you want. + + ![Polar Products](/images/docs/web/billing/polar/products.png) + + Polar takes a different approach to product variants. Instead of having one product with multiple pricing options, Polar treats each pricing option as a separate product. This simplifies the user experience and API while giving you full flexibility. + + At checkout, customers can choose between different products (like monthly or yearly plans), each with its own pricing and benefits. + + ![Polar Product Variants](/images/docs/web/billing/polar/variants.png) + + + You need to make sure that the price ID you set in the configuration matches the ID of the product you created in Polar. + + [See configuration](/docs/web/billing/configuration#prices) for more information. + + + + + ## Create a webhook + + To sync the current subscription status or checkout conclusion and other information to your database, you need to set up a webhook. + + The webhook handling code comes ready to use with TurboStarter, you just have to create the webhook in the Polar dashboard and insert the URL for your project. + + To configure a new webhook, go to the [Webhooks page](https://docs.polar.sh/integrate/webhooks/endpoints) in the Polar settings and click the *Add endpoint* button. + + ![Polar Webhook](/images/docs/web/billing/polar/webhook.png) + + Select the following events: + + * For subscriptions: + * `subscription.created` + * `subscription.updated` + * `subscription.canceled` + * `subscription.revoked` + * For one-off payments: + * `order.created` + + You will also have to enter a *Secret* which you can get by running the following command in your terminal: + + ```bash + openssl rand -base64 32 + ``` + + Copy the generated string and paste it into the *Secret* field. + + You also need to add this secret to your environment variables: + + ```dotenv title="apps/web/.env.local" + POLAR_WEBHOOK_SECRET=your-generated-secret + ``` + + To get the URL for the webhook, you can either use a local development URL or the URL of your deployed app: + + ### Local development + + If you want to test the webhook locally, you can use [ngrok](https://ngrok.com) to create a tunnel to your local machine. Ngrok will then give you a URL that you can use to test the webhook locally. + + To do so, install ngrok and run it with the following command (while your TurboStarter web development server is running): + + ```bash + ngrok http 3000 + ``` + + ![Ngrok](/images/docs/web/billing/stripe/ngrok.png) + + This will give you a URL (see the *Forwarding* output) that you can use to create a webhook in Polar. Just use that url and add `/api/billing/webhook` to it. + + + + ### Production deployment + + When going to production, you will need to set the webhook URL and the events you want to listen to in Polar. + + The webhook path is `/api/billing/webhook`. If your app is hosted at `https://myapp.com` then you need to enter `https://myapp.com/api/billing/webhook` as the URL. + + All the relevant events are automatically handled by TurboStarter, so you don't need to do anything else. If you want to handle more events please check [Webhooks](/docs/web/billing/webhooks) for more information. + + + +## Add discount + +You can add a discount for your customers that will apply on a specific price. + +You can create the discount under the *Products* page on *Discounts* tab in the Polar dashboard. + +![Polar Discount](/images/docs/web/billing/polar/discount.png) + +You can set there a details of discount such as products that it should apply to, amount off, duration, max redemptions and more. + + + +You need to add also the discount code and details to TurboStarter billing configuration to enable displaying it in the UI, creating checkout sessions with it and calculate prices. + +[See discounts configuration](/docs/web/billing/configuration#discounts) for more details. + +That's it! 🎉 You have now set up Polar as a billing provider for your app. + +Feel free to add more products, prices, discounts and manage your customers data and subscriptions using Polar. + + + Make sure that the data you set in the configuration matches the details of things you created in Polar. + + [See configuration](/docs/web/billing/configuration) for more information. + + + +--- +url: /docs/web/billing/stripe +title: Stripe +description: Manage your customers data and subscriptions using Stripe. +--- + +[Stripe](https://stripe.com) is the default billing provider for TurboStarter. Here we'll go through the configuration and how to set it up as a provider for your app. + + + + ## Get API keys + + After you have created your account for [Stripe](https://stripe.com), you will need to get the API key. You can do this by going to the [API page](https://dashboard.stripe.com/apikeys) in the dashboard. Here you will find the *Secret key* and the *Publishable key*. You will need the *Secret key* for the integration to work. + + For local development, make sure to use [Test Mode](https://docs.stripe.com/test-mode) to not mess with the real transactions. + + + + ## Set environment variables + + You need to set the following environment variables: + + ```dotenv title="apps/web/.env.local" + STRIPE_SECRET_KEY="" # Your Stripe secret key + STRIPE_WEBHOOK_SECRET="" # The secret key of the webhook you created (see below) + ``` + + **Please do not add the secret keys to the .env file in production.** During development, you can place them in `.env.local` as it's not committed to the repository. In production, you can set them in the environment variables of your hosting provider. + + + + ## Create products + + For your users to choose from the available subscription plans, you need to create those Products first on the [Products page](https://dashboard.stripe.com/products). You can create as many products as you want. + + Create one product per plan you want to offer. You can add multiple prices within this product to offer multiple models or different billing intervals. + + ![Stripe Products](/images/docs/web/billing/stripe/products.webp) + + + You need to make sure that the price ID you set in the configuration matches the ID of the price you created in Stripe. + + [See configuration](/docs/web/billing/configuration) for more information. + + + + + ## Create a webhook + + To sync the current subscription status or checkout conclusion and other information to your database, you need to set up a webhook. + + The webhook code comes ready to use with TurboStarter, you just have to create the webhook in the Stripe dashboard and insert the URL for your project. + + To configure a new webhook, go to the [Webhooks page](https://dashboard.stripe.com/webhooks) in the Stripe settings and click the Add endpoint button. + + ![Stripe Webhook](/images/docs/web/billing/stripe/webhook.png) + + Select the following events: + + * For subscriptions: + * `customer.subscription.created` + * `customer.subscription.updated` + * `customer.subscription.deleted` + * For one-off payments: + * `checkout.session.completed` + + To get the URL for the webhook, you can either use a local development URL or the URL of your deployed app: + + ### Local development + + There are two ways to test the webhook during local development: + + + + The Stripe CLI which allows you to listen to Stripe events straight to your own localhost. You can install and use the CLI using a variety of methods, but we recommend using official way to do it. + + [Install the Stripe CLI](https://docs.stripe.com/stripe-cli) + + Then - login to your Stripe account using the project you want to run: + + ```bash + stripe login + ``` + + Copy the webhook secret displayed in the terminal and set it as the `STRIPE_WEBHOOK_SECRET` environment variable in your `apps/web/.env.local` file: + + ```dotenv title="apps/web/.env.local" + STRIPE_WEBHOOK_SECRET=*your-secret-key* + ``` + + Now, you can listen to Stripe events running the following command: + + ```bash + stripe listen --forward-to localhost:3000/api/billing/webhook + ``` + + This will forward all the Stripe events to your local endpoint. + + + **If you have not logged in** - the first time you set it up, you are required to sign in. This is a one-time process. Once you sign in, you can use the CLI to listen to Stripe events. + + **Please sign in and then re-run the command.** Now, you can listen to Stripe events. + + If you're not receiving events, please make sure that: + + * the webhook secret is correct + * the account you signed in is the same as the one you're using in your app + + + You can even trigger the event manually for testing purposes: + + ```bash + stripe trigger customer.subscription.created + ``` + + + + + + If you want to test the webhook locally, you can use [ngrok](https://ngrok.com) to create a tunnel to your local machine. Ngrok will then give you a URL that you can use to test the webhook locally. + + To do so, install ngrok and run it with the following command (while your TurboStarter web development server is running): + + ```bash + ngrok http 3000 + ``` + + ![Ngrok](/images/docs/web/billing/stripe/ngrok.png) + + This will give you a URL (see the *Forwarding* output) that you can use to create a webhook in Stripe. Just use that url and add `/api/billing/webhook` to it. + + + + + + ### Production deployment + + When going to production, you will need to set the webhook URL and the events you want to listen to in Stripe. + + The webhook path is `/api/billing/webhook`. If your app is hosted at `https://myapp.com` then you need to enter `https://myapp.com/api/billing/webhook` as the URL. + + All the relevant events are automatically handled by TurboStarter, so you don't need to do anything else. If you want to handle more events please check [Webhooks](/docs/web/billing/webhooks) for more information. + + + + ## Configure Stripe Customer Portal + + Stripe requires you to set up the Customer Portal so that users can manage their billing information, invoices and plan settings from there. + + You can do it [under the following link.](https://dashboard.stripe.com/settings/billing/portal) + + ![Stripe Customer Portal](/images/docs/web/billing/stripe/customer-portal.png) + + 1. Please make sure to enable the setting that lets users switch plans + 2. Configure the behavior of the cancellation according to your needs + + + +## Add discount + +You can add a discount for your customers that will apply on a specific price. + + + + ### Create coupon + + First, you'd need to create a coupon on the [Coupons page](https://dashboard.stripe.com/coupons). + + ![Stripe Coupons](/images/docs/web/billing/stripe/coupon.png) + + You can set there a details of discount such as prices that it should apply to, amount off, duration, max redemptions and more. + + + + ### Add promotion code + + To enable using code during checkout you need to get a promotion code. You can define it on the same page as the coupon and give some user-friendly name to it. + + ![Stripe Promotion Code](/images/docs/web/billing/stripe/promotion-code.png) + + This code will be auto-applied at new checkout sessions. + + + + + + ### Configure discount + + You need to add also the discount code and details to TurboStarter billing configuration to enable displaying it in the UI, creating checkout sessions with it and calculate prices. + + [See discounts configuration](/docs/web/billing/configuration#discounts) for more details. + + + +That's it! 🎉 You have now set up Stripe as a billing provider for your app. + +Feel free to add more products, prices, discounts and manage your customers data and subscriptions using Stripe. + + + Make sure that the data you set in the configuration matches the details of things you created in Stripe. + + [See configuration](/docs/web/billing/configuration) for more information. + + + +--- +url: /docs/web/billing/webhooks +title: Webhooks +description: Handle webhooks from your billing provider. +--- + +TurboStarter handles billing webhooks to update customer data based on events received from the billing provider. + +Occasionally, you may need to set up additional webhooks or perform custom actions with webhooks. + +In such cases, you can customize the billing webhook handler in the billing router at `packages/api/src/modules/billing/router.ts`. + +By default, the webhook handler is configured to be as straightforward as possible: + +```ts title="router.ts" +import { webhookHandler } from "@turbostarter/billing/server"; + +export const billingRouter = new Hono().post("/webhook", (c) => + webhookHandler(c.req.raw), +); +``` + +However, you can extend it using the callbacks provided from `@turbostarter/billing` package: + +```ts title="router.ts" +import { webhookHandler } from "@turbostarter/billing/server"; + +export const billingRouter = new Hono().post("/webhook", (c) => + webhookHandler(c.req.raw, { + onCheckoutSessionCompleted: (sessionId) => {}, + onSubscriptionCreated: (subscriptionId) => {}, + onSubscriptionUpdated: (subscriptionId) => {}, + onSubscriptionDeleted: (subscriptionId) => {}, + onEvent: (rawEvent) => {}, + }), +); +``` + +You can provide one or more of the callbacks to handle the events you are interested in. + + +--- +url: /docs/web/cli +title: CLI +description: Start your new project with a single command. +--- + + + +To help you get started with TurboStarter **as quickly as possible**, we've developed a [CLI](https://www.npmjs.com/package/@turbostarter/cli) that enables you to create a new project (with all the configuration) in seconds. + +The CLI is a set of commands that will help you create a new project, generate code, and manage your project efficiently. + +Currently, the following action is available: + +* **Starting a new project** - Generate starter code for your project with all necessary configurations in place (billing, database, emails, etc.) + +**The CLI is in beta**, and we're actively working on adding more commands and actions. Soon, the following features will be available: + +* **Translations** - Translate your project, verify translations, and manage them effectively +* **Installing plugins** - Easily install plugins for your project +* **Dynamic code generation** - Generate dynamic code based on your project structure + +## Installation + +You can run commands using `npx`: + +```bash +npx turbostarter +npx @turbostarter/cli@latest +``` + + + If you don't want to install the CLI globally, you can simply replace the examples below with `npx @turbostarter/cli@latest` instead of `turbostarter`. + + This also allows you to always run the latest version of the CLI without having to update it. + + +## Usage + +Running the CLI without any arguments will display the general information about the CLI: + +```bash +Usage: turbostarter [options] [command] + +Your Turbo Assistant for starting new projects, adding plugins and more. + +Options: + -v, --version display the version number + -h, --help display help for command + +Commands: + new create a new TurboStarter project + help [command] display help for command +``` + +You can also display help for it or check the actual version. + +### Starting a new project + +Use the `new` command to initialize configuration and dependencies for a new project. + +```bash +npx turbostarter new +``` + +You will be asked a few questions to configure your project: + +```bash +✔ All prerequisites are satisfied, let's start! 🚀 + +? What do you want to ship? › + ◉ Web app + ◉ Mobile app + ◯ Browser extension +? Enter your project name. › +? How do you want to use database? › + Local (powered by Docker) + Cloud +? What do you want to use for billing? › + Stripe + Lemon Squeezy + +... + +🎉 You can now get started. Open the project and just ship it! 🎉 + +Problems? https://www.turbostarter.dev/docs +``` + +It will create a new project, configure providers, install dependencies and start required services in development mode. + + +--- +url: /docs/web/cms/blog +title: Blog +description: Learn how to manage your blog content. +--- + +TurboStarter comes with a pre-configured blog implementation that allows you to manage your blog content. + +## Creating a new blog post + +To create a new blog post, you need to create a new directory (its name will be used as the slug of the blog post) with `.mdx` files in the `packages/cms/src/collections/blog/content` directory. Each file in this directory should be named after the locale it belongs to (e.g `en.mdx`, `es.mdx`, etc.). + +The file will start with a [frontmatter](https://mdxjs.com/guides/frontmatter/) block, which is a yaml-like block that contains metadata about the post. The frontmatter block should be surrounded by three dashes (`---`). + +```mdx title="packages/cms/src/collections/blog/content/my-first-blog-post/en.mdx" +--- +title: Quick Tips to Improve Your Skills Right Away +description: Whether you're learning a new technical skill or working on personal development, these quick tips can help you improve right away. Learn how to break down your goals, practice consistently, and track your progress using Markdown. +publishedAt: 2023-04-19 +tags: [learning, skills, progress] +thumbnail: https://images.unsplash.com/photo-1483639130939-150975af84e5?q=80&w=2370&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D +status: published +--- +``` + +Let's break down the frontmatter fields: + +* `title`: The title of the blog post (it will be also used to generate a slug for the blog post) +* `description`: The description of the blog post +* `publishedAt`: The date when the blog post was published +* `tags`: The tags of the blog post +* `thumbnail`: The thumbnail of the blog post +* `status`: The status of the blog post (could be `published` or `draft`) + +After the frontmatter block, you can add the content of the blog post: + +```mdx title="packages/cms/src/collections/blog/content/my-first-blog-post/en.mdx" +# Quick Tips to Improve Your Skills Right Away + +Awesome paragraph! + +[Link](https://www.turbostarter.dev) + +This is a callout component. + +... +``` + +You can consume the content the same as it's described in [Content Collections](/docs/web/cms/content-collections). + +## BONUS: Using custom components + +As you're using MDX, you can use **any React component** in your blog posts. Just define it as a normal React component and pass it to `` in `components` prop: + +```tsx title="apps/web/src/app/content/page.tsx" +import { MyComponent } from "~/modules/common/my-component"; + +export default function Page() { + return ( + + ); +} +``` + +Then, you would be able to use it in your document content and it will rendered on the page as a result: + +```mdx title="packages/cms/src/collections/blog/content/my-first-blog-post/en.mdx" +... + +# Heading + +Excellent paragraph! + + + +1. First item +2. Second item +3. Third item +``` + +TurboStarter ships with a set of default components that you can use in your blog posts, e.g. ``, `` etc. Use them or define your own to make your blog posts more engaging. + + +--- +url: /docs/web/cms/content-collections +title: Content Collections +description: Get started with Content Collections. +--- + +By default, TurboStarter uses [Content Collections](https://www.content-collections.dev/) to store and retrieve content from the MDX files. + +Content from there is used to populate data in the following places: + +* **Blog** +* **Legal pages** +* **Documentation** + + + It is a great alternative to headless CMS like Contentful or Prismic based on MDX (a more powerful version of markdown). It is free, open source and the content is located right in your repository. + + +Of course, you can add more collections and views, as it's very flexible. + +## Defining new collection + +To define a new collection, you need to create a new file in the `packages/cms/src/collections` directory: + +```ts title="packages/cms/src/collections/legal/index.ts" +import { defineCollection } from "@content-collections/core"; + +export const legal = defineCollection({ + name: "legal", + directory: "src/collections/legal/content", + include: "**/*.mdx", + schema: (z) => ({ + title: z.string(), + description: z.string(), + }), + transform: async (doc, context) => { + const mdx = await transformMDX(doc, context); + + return { + ...mdx, + slug: doc._meta.directory, + locale: doc._meta.fileName.split(".")[0], + }; + }, +}); +``` + +Then it's passed to the config in `packages/cms/content-collections.ts` file which is used to generate types and parse content from MDX files. + +```tsx title="packages/cms/content-collections.ts" +import { defineConfig } from "@content-collections/core"; + +import { legal } from "./src/collections/legal"; + +export default defineConfig({ + collections: [legal], +}); +``` + +When you run a development server, content collections will be automatically rebuilt (in `.content-collections` directory) and you will be able to import the content and metadata of each file in your application. + + + By exporting the generated content you get fully type-safe API to interact + with the content. We can have type safety on the data that we're receiving + from the MDX files. + + +## Using content collections + +To get some content from `@turbostarter/cms` package, you need to use the exposed API that we described in the [Overview section](/docs/web/cms/overview#api): + +```tsx title="apps/web/src/app/[locale](marketing)/legal/[slug]/page.tsx" +import { content } from "@turbostarter/cms"; + +export default async function Page({ + params, +}: { + params: Promise<{ slug: string; locale: string }>; +}) { + const item = getContentItemBySlug({ + collection: CollectionType.LEGAL, + slug: (await params).slug, + locale: (await params).locale, + }); + + return

{title}

; +} +``` + +Voila! You can now access the content from the MDX files. + + + + + + + + +--- +url: /docs/web/cms/overview +title: Overview +description: Manage your content in TurboStarter. +--- + +TurboStarter implements a CMS interface that abstracts the implementation from where you store your data. It provides a simple API to interact with your data, and it's easy to extend and customize. + +By default, the starter kit ships with these implementations in place: + +1. [Content Collections](https://www.content-collections.dev/) - a headless CMS that uses [MDX](https://mdxjs.com/) files to store your content. + +The implementation is available under `@turbostarter/cms` package, here we'll go over how to use it. + +## API + +The CMS package provides a simple, unified API to interact with the content. It's the same for all the providers, so you can easily use it with any of the implementations without changing the code. + +### Fetching content items + +To fetch items from your colletions, you can use the `getContentItems` function. + +```ts +import { getContentItems } from "@turbostarter/cms"; + +const { items, count } = getContentItems({ + collection: CollectionType.BLOG, + tags: [ContentTag.SKILLS], + sortBy: "publishedAt", + sortOrder: SortOrder.DESCENDING, + status: ContentStatus.PUBLISHED, + locale: "en", +}); +``` + +It accepts an object with the following properties: + +* `collection`: The collection to fetch the items from. +* `tags`: The tags to filter the items by. +* `sortBy`: The field to sort the items by. +* `sortOrder`: The order to sort the items in. +* `status`: The status of the items to fetch. It can be `published` or `draft`. By default, only `published` items are fetched. +* `locale`: The locale to fetch the items in. By default, all locales are fetched. + +### Fetching a single content item + +To fetch a single content item, you can use the `getContentItemBySlug` function. + +```ts +import { getContentItemBySlug } from "@turbostarter/cms"; + +const item = getContentItemBySlug({ + collection: CollectionType.BLOG, + slug: "my-first-blog-post", + status: ContentStatus.PUBLISHED, + locale: "en", +}); +``` + +It accepts an object with the following properties: + +* `collection`: The collection to fetch the item from. +* `slug`: The slug of the item to fetch. +* `status`: The status of the item to fetch. It can be `published` or `draft`. By default, only `published` items are fetched. +* `locale`: The locale to fetch the item in. By default, all locales are fetched. + + +--- +url: /docs/web/configuration/app +title: App configuration +description: Learn how to setup the overall settings of your app. +--- + +The application configuration is set at `apps/web/src/config/app.ts`. This configuration stores some overall variables for your application. + +This allows you to host multiple apps in the same monorepo, as every application defines its own configuration. + +The recommendation is to **not update this directly** - instead, please define the environment variables and override the default behavior. The configuration is strongly typed so you can use it safely accross your codebase - it'll be validated at build time. + +```ts title="apps/web/src/config/app.ts" +import env from "env.config"; + +export const appConfig = { + name: env.NEXT_PUBLIC_PRODUCT_NAME, + url: env.NEXT_PUBLIC_URL, + locale: env.NEXT_PUBLIC_DEFAULT_LOCALE, + theme: { + mode: env.NEXT_PUBLIC_THEME_MODE, + color: env.NEXT_PUBLIC_THEME_COLOR, + }, +} as const; +``` + +For example, to set the product name and default locale, you'd update the following variables: + +```dotenv title=".env.local" +NEXT_PUBLIC_PRODUCT_NAME="TurboStarter" +NEXT_PUBLIC_DEFAULT_LOCALE="en" +``` + + + Do NOT use `process.env` to get the values of the variables. Variables + accessed this way are not validated at build time, and thus the wrong variable + can be used in production. + + + +--- +url: /docs/web/configuration/environment-variables +title: Environment variables +description: Learn how to configure environment variables. +--- + +Environment variables are defined in the `.env` file in the root of the repository and in the root of the `apps/web` package. + +* **Shared environment variables**: Defined in the **root** `.env` file. These are shared between environments (e.g., development, staging, production) and apps (e.g., web, mobile). +* **Environment-specific variables**: Defined in `.env.development` and `.env.production` files. These are specific to the development and production environments. +* **App-specific variables**: Defined in the app-specific directory (e.g., `apps/web`). These are specific to the app and are not shared between apps. +* **Secret keys**: Not stored in the `.env` file. Instead, they are stored in the environment variables of the CI/CD system. +* **Local secret keys**: If you need to use secret keys locally, you can store them in the `.env.local` file. This file is not committed to Git, making it safe for sensitive information. + +## Shared variables + +Here you can add all the environment variables that are shared across all the apps. This file should be located in the **root** of the project. + +To override these variables in a specific environment, please add them to the specific environment file (e.g. `.env.development`, `.env.production`). + +```dotenv title=".env.local" +# Shared environment variables + +# The database URL is used to connect to your database. +DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres" + +# The name of the product. This is used in various places across the apps. +PRODUCT_NAME="TurboStarter" + +# The url of the web app. Used mostly to link between apps. +URL="http://localhost:3000" + +... +``` + +If you're using Supabase for your database, the [Supabase recipe](/docs/web/recipes/supabase#configure-environment-variables) shows the exact `DATABASE_URL` format and how to set it in your `.env.local`. + +## App-specific variables + +Here you can add all the environment variables that are specific to the app (e.g. `apps/web`). + +You can also override the shared variables defined in the root `.env` file. + +```dotenv title="apps/web/.env.local" +# App-specific environment variables + +# Env variables extracted from shared to be exposed to the client in Next.js app +NEXT_PUBLIC_PRODUCT_NAME="${PRODUCT_NAME}" +NEXT_PUBLIC_URL="${URL}" +NEXT_PUBLIC_DEFAULT_LOCALE="${DEFAULT_LOCALE}" + +# Theme mode and color +NEXT_PUBLIC_THEME_MODE="system" +NEXT_PUBLIC_THEME_COLOR="orange" + +... +``` + + + To make environment variables available in the Next.js **client-side** app code, you need to prefix them with `NEXT_PUBLIC_`. They will be injected to the code during the build process. + + Only environment variables prefixed with `NEXT_PUBLIC_` will be injected, so don't use this prefix for environment variables that should be used only in the server-side code. + + [Read more about Next.js environment variables.](https://nextjs.org/docs/pages/building-your-application/configuring/environment-variables) + + +## Secret keys + +Secret keys and sensitive information are to be never stored in the `.env` file. Instead, **they are stored in the environment variables of the CI/CD system.** + + + It means that you will need to add the secret keys to the environment + variables of your CI/CD system (e.g., GitHub Actions, Vercel, Cloudflare, your + VPS, Netlify, etc.). This is not a TurboStarter-specific requirement, but a + best practice for security for any application. Ultimately, it's your choice. + + +Below is some examples of "what is a secret key?" in practice. + +```dotenv title=".env.local" +# Secret keys + +# The database URL is used to connect to your database. +DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres" + +# Stripe server config - required only if you use Stripe as a billing provider +STRIPE_WEBHOOK_SECRET="" +STRIPE_SECRET_KEY="" + +# Lemon Squeezy server config - required only if you use Lemon Squeezy as a billing provider +LEMON_SQUEEZY_API_KEY="" +LEMON_SQUEEZY_SIGNING_SECRET="" +LEMON_SQUEEZY_STORE_ID="" + +... +``` + + + If you need to use secret keys locally, you can store them in the `.env.local` + file. This file is not committed to Git, therefore it is safe to store + sensitive information in it. + + + +--- +url: /docs/web/configuration/paths +title: Paths configuration +description: Learn how to configure the paths of your app. +--- + +The paths configuration is set at `apps/web/config/paths.ts`. This configuration stores all the paths that you'll be using in your application. It is a convenient way to store them in a central place rather than scatter them in the codebase using magic strings. + +It is **unlikely you'll need to change** this unless you're heavily editing the codebase. + +```ts title="apps/web/config/paths.ts" +const pathsConfig = { + index: "/", + marketing: { + pricing: "/pricing", + contact: "/contact", + blog: { + index: BLOG_PREFIX, + post: (slug: string) => `${BLOG_PREFIX}/${slug}`, + }, + legal: (slug: string) => `${LEGAL_PREFIX}/${slug}`, + }, + auth: { + login: `${AUTH_PREFIX}/login`, + register: `${AUTH_PREFIX}/register`, + join: `${AUTH_PREFIX}/join`, + forgotPassword: `${AUTH_PREFIX}/password/forgot`, + updatePassword: `${AUTH_PREFIX}/password/update`, + error: `${AUTH_PREFIX}/error`, + }, + dashboard: { + user: { + index: DASHBOARD_PREFIX, + ai: `${DASHBOARD_PREFIX}/ai`, + settings: { + index: `${DASHBOARD_PREFIX}/settings`, + security: `${DASHBOARD_PREFIX}/settings/security`, + billing: `${DASHBOARD_PREFIX}/settings/billing`, + }, + }, + ... + }, + ..., +} as const; +``` + + + By declaring the paths as constants, we can use them safely throughout the + codebase. There is no risk of misspelling or using magic strings. + + + +--- +url: /docs/web/customization/add-app +title: Adding apps +description: Learn how to add apps to your Turborepo workspace. +--- + + + This is an **advanced topic** - you should only follow these instructions if you are sure you want to add a new app to your TurboStarter project within your monorepo and want to keep pulling updates from the TurboStarter repository. + + +In some ways - creating a new repository may be the easiest way to manage your application. However, if you want to keep your application within the monorepo and pull updates from the TurboStarter repository, you can follow these instructions. + +To pull updates into a separate application outside of `web` - we can use [git subtree](https://www.atlassian.com/git/tutorials/git-subtree). + +Basically, we will create a subtree at `apps/web` and create a new remote branch for the subtree. When we create a new application, we will pull the subtree into the new application. This allows us to keep it in sync with the `apps/web` folder. + +To add a new app to your TurboStarter project, you need to follow these steps: + + + + ## Create a subtree + + First, we need to create a subtree for the `apps/web` folder. We will create a branch named `web-branch` and create a subtree for the `apps/web` folder. + + ```bash + git subtree split --prefix=apps/web --branch web-branch + ``` + + + + ## Create a new app + + Now, we can create a new application in the `apps` folder. + + Let's say we want to create a new app `ai-chat` at `apps/ai-chat` with the same structure as the `apps/web` folder (which acts as the template for all new apps). + + ```bash + git subtree add --prefix=apps/ai-chat origin web-branch --squash + ``` + + You should now be able to see the `apps/ai-chat` folder with the contents of the `apps/web` folder. + + + + ## Update the app + + When you want to update the new application, follow these steps: + + ### Pull the latest updates from the TurboStarter repository + + The command below will update all the changes from the TurboStarter repository: + + ```bash + git pull upstream main + ``` + + ### Push the `web-branch` updates + + After you have pulled the updates from the TurboStarter repository, you can split the branch again and push the updates to the web-branch: + + ```bash + git subtree split --prefix=apps/web --branch web-branch + ``` + + Now, you can push the updates to the `web-branch`: + + ```bash + git push origin web-branch + ``` + + ### Pull the updates to the new application + + Now, you can pull the updates to the new application: + + ```bash + git subtree pull --prefix=apps/ai-chat origin web-branch --squash + ``` + + + +That's it! You now have a new application in the monorepo 🎉 + + +--- +url: /docs/web/customization/add-package +title: Adding packages +description: Learn how to add packages to your Turborepo workspace. +--- + + + This is an **advanced topic** - you should only follow these instructions if you are sure you want to add a new package to your TurboStarter application instead of adding a folder to your application in `apps/web` or modify existing packages under `packages`. You don't need to do this to add a new page or component to your application. + + +To add a new package to your TurboStarter application, you need to follow these steps: + + + + ## Generate a new package + + First, enter the command below to create a new package in your TurboStarter application: + + ```bash + turbo gen package + ``` + + Turborepo will ask you to enter the name of the package you want to create. Enter the name of the package you want to create and press enter. + + If you don't want to add dependencies to your package, you can skip this step by pressing enter. + + The command will have generated a new package under packages named `@turbostarter/`. If you named it `example`, the package will be named `@turbostarter/example`. + + Finally, to make fast refresh work when you make changes to the package, you need to add the package to the `next.config.ts` file in the root of your TurboStarter application `apps/web`. + + ```ts title="next.config.ts" + const INTERNAL_PACKAGES = [ + // all internal packages, + "@turbostarter/example", + ]; + ``` + + + + ## Export a module from your package + + By default, the package exports a single module using the `index.ts` file. You can add more exports by creating new files in the package directory and exporting them from the `index.ts` file or creating export files in the package directory and adding them to the `exports` field in the `package.json` file. + + ### From `index.ts` file + + The easiest way to export a module from a package is to create a new file in the package directory and export it from the `index.ts` file. + + ```ts title="packages/example/src/module.ts" + export function example() { + return "example"; + } + ``` + + Then, export the module from the `index.ts` file. + + ```ts title="packages/example/src/index.ts" + export * from "./module"; + ``` + + ### From `exports` field in `package.json` + + **This can be very useful for tree-shaking.** Assuming you have a file named `module.ts` in the package directory, you can export it by adding it to the `exports` field in the `package.json` file. + + ```json title="packages/example/package.json" + { + "exports": { + ".": "./src/index.ts", + "./module": "./src/module.ts" + } + } + ``` + + **When to do this?** + + 1. when exporting two modules that don't share dependencies to ensure better tree-shaking. For example, if your exports contains both client and server modules. + 2. for better organization of your package + + For example, create two exports `client` and `server` in the package directory and add them to the `exports` field in the `package.json` file. + + ```json title="packages/example/package.json" + { + "exports": { + ".": "./src/index.ts", + "./client": "./src/client.ts", + "./server": "./src/server.ts" + } + } + ``` + + 1. The `client` module can be imported using `import { client } from '@turbostarter/example/client'` + 2. The `server` module can be imported using `import { server } from '@turbostarter/example/server'` + + + + ## Use the package in your application + + You can now use the package in your application by importing it using the package name: + + ```ts title="apps/web/src/app/page.tsx" + import { example } from "@turbostarter/example"; + + console.log(example()); + ``` + + + +Et voilà! You have successfully added a new package to your TurboStarter application. 🎉 + + +--- +url: /docs/web/customization/components +title: Components +description: Manage and customize your app components. +--- + +For the components part, we're using [shadcn/ui](https://ui.shadcn.com) for atomic, accessible and highly customizable components. + + + shadcn/ui is a powerful tool that allows you to generate pre-designed + components with a single command. It's built with Tailwind CSS and Radix UI, + and it's highly customizable. + + +TurboStarter defines two packages that are responsible for the UI part of your app: + +* `@turbostarter/ui` - shared styles, [themes](/docs/web/customization/styling#themes) and assets (e.g. icons) +* `@turbostarter/ui-web` - pre-built UI web components, ready to use in your app + +## Adding a new component + +There are basically two ways to add a new component: + + + + TurboStarter is fully compatible with [shadcn CLI](https://ui.shadcn.com/docs/cli), so you can generate new components with single command. + + Run the following command from the **root** of your project: + + ```bash + pnpm --filter @turbostarter/ui-web ui:add + ``` + + This will launch an interactive command-line interface to guide you through the process of adding a new component where you can pick which component you want to add. + + ```bash + Which components would you like to add? > Space to select. A to toggle all. + Enter to submit. + + ◯ accordion + ◯ alert + ◯ alert-dialog + ◯ aspect-ratio + ◯ avatar + ◯ badge + ◯ button + ◯ calendar + ◯ card + ◯ checkbox + ``` + + Newly created components will appear in the `packages/ui/web/src` directory. + + + + You can always copy-paste a component from the [shadcn/ui](https://ui.shadcn.com/docs/components) website and modify it to your needs. + + This is possible, because the components are headless and don't need (in most cases) any additional dependencies. + + Copy code from the website, create a new file in the `packages/ui/web/src` directory and paste the code into the file. + + + + + Keep in mind that you should always try to keep shared components as atomic as possible. This will make it easier to reuse them and to build specific views by composition. + + E.g. include components like `Button`, `Input`, `Card`, `Dialog` in shared package, but keep specific components like `LoginForm` in your app directory. + + +## Using components + +Each component is a standalone entity which has a separate export from the package. It helps to keep things modular, avoid unnecessary dependencies and make tree-shaking possible. + +To import a component from the UI package, use the following syntax: + +```tsx title="components/my-component.tsx" +// [!code word:card] +import { + Card, + CardContent, + CardHeader, + CardFooter, + CardTitle, + CardDescription, +} from "@turbostarter/ui-web/card"; +``` + +Then you can use it to build a component specific to your app: + +```tsx title="components/my-component.tsx" +export function MyComponent() { + return ( + + + My Component + + +

My Component Content

+
+ + + +
+ ); +} +``` + + + We recommend using [v0](https://v0.dev) to generate layouts for your app. It's a powerful tool that allows you to generate layouts from the natural language instructions. + + Of course, **it won't replace a designer**, but it can be a good starting point for your layout. + + + + + + + + + +--- +url: /docs/web/customization/styling +title: Styling +description: Get started with styling your app. +--- + +To build the web user interface, TurboStarter comes with [Tailwind CSS](https://tailwindcss.com/) and [Radix UI](https://www.radix-ui.com/) pre-configured. + + + The combination of Tailwind CSS and Radix UI gives ready-to-use, accessible UI components that can be fully customized to match your brand's design. + + +## Tailwind configuration + +In the `packages/ui/shared/src/styles` directory, you will find shared CSS files with Tailwind CSS configuration. To change global styles, you can edit the files in this folder. + +Here is an example of a shared CSS file that includes the Tailwind CSS configuration: + +```css title="packages/ui/shared/src/styles/globals.css" +@import "tailwindcss"; +@import "./themes.css"; + +@custom-variant dark (&:is(.dark *)); + +:root { + --radius: 0.65rem; +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + + ... +} +``` + +For colors, we rely strictly on [CSS Variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties) in [OKLCH](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch) format to allow for easy theme management without the need for any JavaScript. + +Also, each app has its own `globals.css` file, which extends the shared config and allows you to override the global styles. + +Here is an example of an app's `globals.css` file: + +```css title="apps/web/src/assets/styles/globals.css" +@import "@turbostarter/ui/globals.css"; +@import "@turbostarter/ui-web/globals.css"; + +@theme inline { + /* Overridden theme variables for the app */ + --background: oklch(0.98 0.01 80); + --foreground: oklch(0.22 0.03 120); + --card: oklch(0.97 0.02 50); + --card-foreground: oklch(0.18 0.01 280); + ... +} +``` + +This way, we maintain a separation of concerns and a clear structure for the Tailwind CSS configuration. + +## Themes + +TurboStarter comes with **9+** predefined themes, which you can use to quickly change the look and feel of your app. + +They're defined in the `packages/ui/shared/src/styles/themes` directory. Each theme is a set of variables that can be overridden: + +```ts title="packages/ui/shared/src/styles/themes/colors/orange.ts" +export const orange = { + light: { + background: [1, 0, 0], + foreground: [0.141, 0.005, 285.823], + card: [1, 0, 0], + "card-foreground": [0.141, 0.005, 285.823], + ... + } +} satisfies ThemeColors; +``` + +Each variable is stored as a [OKLCH](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch) array, which is then converted to a CSS variable at build time (by our custom build script). That way we can ensure full type-safety and reuse themes across different parts of our apps (e.g. use the same theme in emails). + +Feel free to add your own themes or override the existing ones to match your brand's identity. + +To apply a theme to your app, you can use the `data-theme` attribute on the `html` element: + +```tsx title="apps/web/src/app/layout.tsx" +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} +``` + +## Dark mode + +TurboStarter comes with built-in dark mode support. + +Each theme has a corresponding set of dark mode variables, which are used to switch the theme to its dark mode counterpart. + +```ts title="packages/ui/shared/src/styles/themes/colors/orange.ts" +export const orange = { + light: {}, + dark: { + background: [0.141, 0.005, 285.823], + foreground: [0.985, 0, 0], + card: [0.21, 0.006, 285.885], + "card-foreground": [0.985, 0, 0], + ... + } +} satisfies ThemeColors; +``` + +Because the dark variant is defined to use a class (`@custom-variant dark (&:is(.dark *))`) in the shared Tailwind configuration, we need to add the `dark` class to the `html` element to apply dark mode styles. + +For this purpose, we're using the [next-themes](https://github.com/pacocoursey/next-themes) package under the hood to handle user preference management. + +```tsx title="apps/web/src/providers/theme.tsx" +export const ThemeProvider = memo(({ children }) => { + return ( + + {children} + + + ); +}); +``` + +You can also define the default theme mode and color in the [app configuration](/docs/web/configuration/app). + + + + + + + + +--- +url: /docs/web/database/client +title: Database client +description: Use database client to interact with the database. +--- + +The database client is an export of the Drizzle client. It is automatically typed by Drizzle based on the schema and is exposed as the db object from the database package (`@turbostarter/db`) in the monorepo. + +This guide covers how to initialize the client and also basic operations, such as querying, creating, updating, and deleting records. To learn more about the Drizzle client, check out the [official documentation](https://orm.drizzle.team/kit-docs/overview). + +## Initializing the client + +Pass the validated `DATABASE_URL` to the client to initialize it. + +```ts title="server.ts" +import { drizzle } from "drizzle-orm/postgres-js"; +import postgres from "postgres"; + +import { env } from "../env"; + +const client = postgres(env.DATABASE_URL); +export const db = drizzle(client); +``` + +Now it's exported from the `@turbostarter/db` package and can be used across the codebase (server-side). + +## Querying data + +To query data, you can use the `db` object and its methods: + +```ts title="query.ts" +import { eq } from "@turbostarter/db"; +import { db } from "@turbostarter/db/server"; +import { customer } from "@turbostarter/db/schema"; + +export const getCustomerByUserId = async (userId: string) => { + const [data] = await db + .select() + .from(customer) + .where(eq(customer.userId, userId)); + + return data ?? null; +}; +``` + + + + + + + + + +## Mutating data + +You can use the exported utilities to mutate data. Insert, update or delete records in fast and fully type-safe way: + +```ts title="mutation.ts" +import { eq } from "@turbostarter/db"; +import { db } from "@turbostarter/db/server"; +import { customer } from "@turbostarter/db/schema"; + +export const upsertCustomer = (data: InsertCustomer) => { + return db.insert(customer).values(data).onConflictDoUpdate({ + target: customer.userId, + set: data, + }); +}; +``` + + + + + + + + + + +--- +url: /docs/web/database/migrations +title: Migrations +description: Migrate your changes to the database. +--- + +You have your schema in place, and you want to apply your changes to the database. TurboStarter provides you a convenient way to do so with pre-configured CLI commands. + +## Generating migration + +To generate a migration, from the schema you need to run the following command: + +```bash +pnpm with-env turbo db:generate +``` + +This will create a new `.sql` file in the `migrations` directory. + + + Drizzle will also generate a `.json` representation of the migration in the `meta` directory, but it's for its internal purposes and you shouldn't need to touch it. + + +## Applying migrations + +To apply the migrations to the database, you need to run the following command: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:migrate +``` + +This will apply all the migrations that have not been applied yet. If any conflicts arise, you can resolve them by modifying the generated migration file. + +## Pushing changes + +To push changes directly to the database, you can use the following command: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:push +``` + +This lets you push your schema changes directly to the database and omit managing SQL migration files. + + + Pushing changes directly to the database (without using migrations) could be risky. Please be careful when using it; we recommend it only for local development and local databases. + + [Read more about it in the Drizzle docs](https://orm.drizzle.team/kit-docs/overview#prototyping-with-db-push). + + + +--- +url: /docs/web/database/overview +title: Overview +description: Get started with the database. +--- + +We're using [Drizzle ORM](https://orm.drizzle.team) to interact with the database. It basically adds a little layer of abstraction between our code and the database. + +> If you know SQL, you know Drizzle. + +For the database we're leveraging [PostgreSQL](https://www.postgresql.org), but you could use any other database that Drizzle ORM supports (basically any SQL database e.g. [MySQL](https://orm.drizzle.team/docs/get-started-mysql), [SQLite](https://orm.drizzle.team/docs/get-started-sqlite), etc.). + + + Drizzle ORM is a powerful tool that allows you to interact with the database in a type-safe manner. It ships with **0** (!) dependencies and is designed to be fast and easy to use. + + +## Setup + +To start interacting with the database you first need to ensure that your database service instance is up and running. + + + + For local development we recommend using the [Docker](https://hub.docker.com/_/postgres) container. + + You can start the container with the following command: + + ```bash + pnpm services:setup + ``` + + This will start all the services (including the database container) and initialize the database with the latest schema. + + **Where is DATABASE\_URL?** + + `DATABASE_URL` is a connection string that is used to connect to the database. When the command will finish it will be displayed in the console and setup to your environment variables. + + + + You can also use a cloud instance of database (e.g. [Supabase](/docs/web/recipes/supabase), [Neon](https://neon.tech/), [Turso](https://turso.tech/), etc.), although it's not recommended for local development. + + If you choose Supabase as your provider, follow the [Supabase recipe](/docs/web/recipes/supabase#configure-environment-variables) for details on configuring `DATABASE_URL` and running migrations. + + **Where is DATABASE\_URL?** + + It's available in your provider's project dashboard. You'll need to copy the connection string from there and add it to your `.env.local` file. The format will look something like: + + * Neon: `postgresql://user:password@ep-xyz-123.region.aws.neon.tech/dbname` + * Turso: `libsql://your-db-xyz.turso.io` + + Make sure to keep this URL secure and never commit it to version control. + + + +Then, you need to set `DATABASE_URL` environment variable in **root** `.env.local` file. + +```dotenv title=".env.local" +# The database URL is used to connect to your database. +DATABASE_URL="postgresql://postgres:postgres@127.0.0.1:54322/postgres" +``` + +You're ready to go! 🥳 + +## Studio + +TurboStarter provides you also with an interactive UI where you can explore your database and test queries called Studio. + +To run the Studio, you can use the following command: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:studio +``` + +This will start the Studio on [https://local.drizzle.studio](https://local.drizzle.studio). + +![Drizzle Studio](/images/docs/db-studio.webp) + +## Next steps + +* [Update schema](/docs/web/database/schema) - learn about schema and how to update it. +* [Generate & run migrations](/docs/web/database/migrations) - migrate your changes to the database. +* [Initialize client](/docs/web/database/client) - initialize the database client and start interacting with the database. + + +--- +url: /docs/web/database/schema +title: Schema +description: Learn about the database schema. +--- + +Creating a schema for your data is one of the primary tasks when building a new application. + +You can find the schema of each table in `packages/db/src/db/schema` directory. The schema is basically organized by entity and each file is a separate table. + +## Defining schema + +The schema is defined using SQL-like utilities from [drizzle-orm](https://orm.drizzle.team/docs/sql-schema-declaration). + +It supports all the SQL features, such as enums, indexes, foreign keys, extensions and more. + + + We're relying on the [code-first approach](https://orm.drizzle.team/docs/migrations), where we define the schema in code and then generate the SQL from it. That way we can approach full type-safety and the simplest flow for database updates and migrations. + + +## Example + +Let's take a look at the `customer` table, where we store information about our customers. + +```typescript title="customer.ts" +export const customer = pgTable("customer", { + id: text().primaryKey().$defaultFn(generateId), + userId: text() + .references(() => user.id, { + onDelete: "cascade", + }) + .notNull() + .unique(), + customerId: text().notNull().unique(), + status: billingStatusEnum(), + plan: pricingPlanTypeEnum(), + createdAt: timestamp().notNull().defaultNow(), + updatedAt: timestamp() + .notNull() + .$onUpdate(() => new Date()), +}); +``` + +We're using a few native SQL utilities here, such as: + +* `pgTable` - a table definition. +* `primaryKey` - a primary key. +* `defaultFn` - a default function. +* `$onUpdate` - an on update function. +* `notNull` - a not null constraint. +* `defaultNow` - a default now function. +* `timestamp` - a timestamp. +* `text` - a text. +* `unique` - a unique constraint. +* `references` - a reference to another table. + +What's more, Drizzle gives us the ability to export the TypeScript types for the table, which we can reuse e.g. for the API calls. + +Also, we can use the drizzle extension [drizzle-zod](https://orm.drizzle.team/docs/zod) to generate the Zod schemas for the table. + +```typescript title="customer.ts" +import { createInsertSchema, createSelectSchema } from "drizzle-zod"; + +export const insertCustomerSchema = createInsertSchema(customer); +export const selectCustomerSchema = createSelectSchema(customer); +export const updateCustomerSchema = createUpdateSchema(customer); + +export type InsertCustomer = z.infer; +export type SelectCustomer = z.infer; +export type UpdateCustomer = z.infer; +``` + +Then we can use the generated schemas in our API handlers and frontend forms to validate the data. + + +--- +url: /docs/web/deployment/amplify +title: AWS Amplify +description: Learn how to deploy your TurboStarter app to AWS Amplify. +--- + +[AWS Amplify](https://aws.amazon.com/amplify/) is a fully managed service that makes it easy to build, deploy, and host modern web applications. It provides features like continuous deployment, serverless functions, authentication, and more - all integrated into a seamless developer experience. + +This guide explains how to deploy your TurboStarter app on AWS Amplify. You'll learn how to set up your repository for automated deployments, configure build settings, manage environment variables, and ensure your application runs smoothly in production. **AWS Amplify handles the infrastructure management, allowing you to focus on developing your application.** + + + To deploy to AWS Amplify, you need to have an AWS account. You can create one [here](https://aws.amazon.com/amplify/). + + + + + ## Create configuration file + + To deploy your TurboStarter app to AWS Amplify, you need to create a config file. This file will contain the necessary information to connect your repository to AWS Amplify and deploy your application. + + Let's create a new file called `amplify.yml` in the root of your project: + + ```yaml title="amplify.yml" + version: 1 + applications: + - frontend: + buildPath: "/" + phases: + preBuild: + commands: + - npm install -g pnpm + - pnpm install + build: + commands: + - pnpm dlx turbo build --filter=web + artifacts: + baseDirectory: apps/web/.next + files: + - "**/*" + cache: + paths: + - node_modules/**/* + - apps/web/.next/cache/**/* + appRoot: apps/web + ``` + + This configuration file tells AWS Amplify how to build and deploy your application: + + * The `version` field specifies the Amplify configuration version + * Under `applications`, we define the build settings for our web app: + * `buildPath` indicates where to run the build commands + * `preBuild` phase installs pnpm and project dependencies + * `build` phase runs the Turborepo build command for the web app + * `artifacts` specifies which files to deploy (the Next.js build output) + * `cache` configures which directories to cache between builds + * `appRoot` points to the web application directory + + AWS Amplify will use this configuration to automatically build and deploy your app whenever you push changes to your repository. It also useful to define other resources that you can use and link to your project. + + + + ## Create a new Amplify project + + We'll use the [AWS Amplify](https://aws.amazon.com/amplify/) web interface to deploy our app. First, let's create a new project. + + ![Amplify create project](/images/docs/web/deployment/amplify/create-project.png) + + Proceed with the option to *Deploy an app*. + + + + ## Connect repository + + Choose the Git provider of your project and select the repository you want to deploy. + + ![Amplify connect repository](/images/docs/web/deployment/amplify/connect-repository.png) + + + If your repository is private you need to authorize Amplify to access it. It's recommended to follow a *least privileged access* approach, so to only grant access to the repository you want to deploy, not the entire account. + + + Select the branch you want to deploy and make sure to enable the *My app is a monorepo* option - configure it with the path to the app that you want to deploy (e.g. `apps/web`). + + ![Amplify repository and branch](/images/docs/web/deployment/amplify/repository.png) + + + + ## Configure build settings + + Finalize your deployment by configuring the build settings to match your project's specific needs. Refer to the points below to ensure a seamless deployment process. + + ![Amplify build settings](/images/docs/web/deployment/amplify/build-settings.png) + + Make sure that the build command and build output directory is set to the correct values (it should be defined based on your configuration file from Step 1.). + + ### Environment variables + + In the *Advanced settings* section, you can define environment variables that will be available to your application at runtime. + + ![Amplify environment variables](/images/docs/web/deployment/amplify/environment-variables.png) + + Verify that all required environment variables are defined, so your app can be build and deployed successfully. + + + + ## Review and deploy! + + On the next step, you'll be able to review the configuration that you've created and deploy your app. It's the right time to make sure that everything is set up correctly. + + ![Amplify review and deploy](/images/docs/web/deployment/amplify/review.png) + + After making sure that everything is set up correctly, you can click on the *Save and deploy* button to start the deployment process. + + When your app is deployed, you'll be able to access it via the URL provided in the Amplify console: + + ![Amplify deployed app](/images/docs/web/deployment/amplify/deployed.png) + + That's it! Your app is now deployed to AWS Amplify, congratulations! 🎉 + + + +Feel free to scale your deployment to multiple regions, add custom domains, and use other Amplify features to make your app more robust and scalable. +Check out the [AWS Amplify documentation](https://docs.aws.amazon.com/amplify/latest/userguide/welcome.html) for more information on how to use Amplify to its full potential. + + +--- +url: /docs/web/deployment/api +title: Standalone API +description: Learn how to deploy your API as a dedicated service. +--- + +Sometimes you want to deploy your API as a standalone service. This is useful if you want to deploy your API to a different domain or to deploy it as a microservice. You can also follow this approach if you don't need a web app, but still need API service for [mobile app](/docs/mobile) or [browser extension](/docs/extension). + +Deploying your API as a standalone service provides enhanced flexibility and scalability. This allows you to independently scale your API from your web app. It's particularly beneficial for executing "long-running" tasks on your backend, such as report generation, real-time data processing, or background tasks that are likely to timeout in a serverless environment. + +This guide explains how to deploy your TurboStarter API as a standalone service. As Hono has multiple deployment options (e.g. [Deno](https://hono.dev/docs/getting-started/deno), [Bun](https://hono.dev/docs/getting-started/bun)), this guide will focus primarily on the [Node.js](https://hono.dev/docs/getting-started/nodejs) deployment. + + + + ## Create separate API app + + We have a [dedicated guide](/docs/web/customization/add-app) on how to add another app to your project. However, in this case, only a few files need to be added, so we can do it quickly here. + + First, let's create an `api` directory inside the `apps` directory - it will be the root of your API app. + + Next, add the following files into the `apps/api` directory: + + + + ```json + { + "name": "api", + "version": "0.1.0", + "private": true, + "scripts": { + "build": "esbuild ./src/index.ts --bundle --platform=node --outfile=dist/index.js", + "clean": "git clean -xdf dist .turbo node_modules", + "dev": "dotenv -c -- tsx watch src/index.ts", + "start": "node dist/index.js", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@hono/node-server": "1.13.7", + "@turbostarter/api": "workspace:*" + }, + "devDependencies": { + "@turbostarter/tsconfig": "workspace:*", + "@types/node": "20.16.10", + "esbuild": "0.24.2", + "tsx": "4.19.2", + "typescript": "catalog:" + } + } + ``` + + + + ```json + { + "extends": "@turbostarter/tsconfig/base.json", + "include": ["src"], + "exclude": ["node_modules"] + } + ``` + + + + ```ts + import { serve } from "@hono/node-server"; + import { appRouter } from "@turbostarter/api"; + + serve( + { + fetch: appRouter.fetch, + port: Number(process.env.PORT) || 3001, + }, + ({ port }) => { + console.log(`Server is running on ${port} 🚀`); + }, + ); + ``` + + + + This will enable you to have a minimal configuration required to run your API as a standalone service. For sure, you can add more configuration (e.g. ESLint or Prettier) if needed, we just want to keep it minimal for the sake of this guide. + + + + ## Connect web app to API + + The API will be running on a different URL than your web app. For the minimal setup and to avoid handling [cross-origin resource sharing (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) issues, we will rewrite the API URL in the web app. + + To do this, you will need to change your `next.config.ts` file to include the API URL rewrite: + + ```js title="apps/web/next.config.ts" + import type { NextConfig } from "next"; + + const config: NextConfig = { + rewrites: async () => [ + { + source: "/api/:path*", + destination: `${env.NEXT_PUBLIC_API_URL ?? "http://localhost:3001"}/api/:path*`, + }, + ], + }; + ``` + + + It's recommended to use an environment variable (e.g. `NEXT_PUBLIC_API_URL`) to set the API URL. This is a good practice to make it easier to change the API URL in different environments (e.g. development, staging, production). + + + Now you should be able to run your API as a standalone service. When you run the project with `pnpm dev`, you will see the new app called `api` with your API server running on [http://localhost:3001](http://localhost:3001). + + + + ## Deploy! + + You can basically deploy your API as any other Node.js project. We will quickly go through the two most popular options: [PaaS](https://en.wikipedia.org/wiki/Platform_as_a_service) and [Docker](https://www.docker.com/). + + ### Platform as a Service (PaaS) + + PaaS providers like [Vercel](https://vercel.com/), [Heroku](https://www.heroku.com/), or [Netlify](https://www.netlify.com/) allow you to deploy your Node.js app with a few clicks. You can follow our [dedicated guides](/docs/web/deployment/checklist#deploy-web-app-to-production) for the most popular providers. Every process is similar, and will contains a few crucial steps: + + 1. Connecting your repository to the PaaS provider + 2. Setting up build settings (e.g. build command, output directory) + 3. Setting up environment variables + 4. Deploying the project + + + To make sure your API is built and run correctly, you will need to ensure that appropriate commands are correctly set up. In our case, the following commands will need to be configured: + + + + ```bash + pnpm turbo build --filter=api + ``` + + + + ```bash + pnpm --filter=api start + ``` + + + + This is required to ensure that the PaaS provider of your choice will be able to build and run your application correctly. + + + ### Docker + + Deploying your API as a Docker container is a good option if you want to have more control over the deployment process. You can follow our [dedicated guide](/docs/web/deployment/docker) to learn how to deploy your API as a Docker container. + + For the API application, the `Dockerfile` will be located in the `apps/api` directory and it could look like this: + + ```dockerfile title="apps/api/Dockerfile" + FROM node:20-alpine AS base + ENV PNPM_HOME="/pnpm" + ENV PATH="$PNPM_HOME:$PATH" + RUN corepack enable + + FROM base AS pruner + WORKDIR /app + RUN apk add --no-cache libc6-compat + COPY . . + RUN pnpm dlx turbo prune api --docker + + FROM base AS builder + WORKDIR /app + RUN apk add --no-cache libc6-compat + COPY --from=pruner /app/out/json/ . + COPY --from=pruner /app/out/pnpm-lock.yaml ./pnpm-lock.yaml + RUN pnpm install --frozen-lockfile --ignore-scripts --prefer-offline && pnpm store prune + ENV SKIP_ENV_VALIDATION=1 \ + NODE_ENV=production + COPY --from=pruner /app/out/full/ . + RUN pnpm dlx turbo build --filter=api + + FROM base AS runner + WORKDIR /app + RUN addgroup -g 1001 -S nodejs && \ + adduser -S api -u 1001 -G nodejs + COPY --from=builder --chown=api:nodejs /app/apps/api/dist/ ./ + USER api + EXPOSE 3001 + CMD ["node", "index.js"] + ``` + + To test if everything works correctly, you can run a [container](https://docs.docker.com/get-started/03_run_your_app/) locally with the following commands: + + ```bash + docker build -f ./apps/api/Dockerfile . -t turbostarter-api + docker run -p 3001:3001 turbostarter-api + ``` + + Make sure to also [pass](https://docs.docker.com/reference/cli/docker/container/run/#env) all the required environment variables to the container, so your API can start without any issues. + + Deploying your API as a Docker container is a great way to isolate your API from the host environment, making it easier to deploy and scale. It also simplifies the workflow if you're working with a team, as you can easily share the Docker image with your colleagues and they will run the API in the **exact same** environment. + + + +That's it! You can now grow your API layer as a standalone service, separated from other apps in your project, and deploy it anywhere you want. + + +--- +url: /docs/web/deployment/checklist +title: Checklist +description: Let's deploy your TurboStarter app to production! +--- + +When you're ready to deploy your project to production, follow this checklist. + +This process may take a few hours and some trial and error, so buckle up - you're almost there! + + + + ## Create database instance + + **Why it's necessary?** + + A production-ready database instance is essential for storing your application's data securely and reliably in the cloud. [PostgreSQL](https://www.postgresql.org/) is the recommended database for TurboStarter due to its robustness, features, and wide support. + + **How to do it?** + + You have several options for hosting your PostgreSQL database: + + * [Supabase](/docs/web/recipes/supabase) - Provides a fully managed Postgres database with additional features + * [Vercel Postgres](https://vercel.com/storage/postgres) - Serverless SQL database optimized for Vercel deployments + * [Neon](https://neon.tech/) - Serverless Postgres with automatic scaling + * [Turso](https://turso.tech/) - Edge database built on libSQL with global replication + * [DigitalOcean](https://www.digitalocean.com/products/managed-databases) - Managed database clusters with automated failover + + Choose a provider based on your needs for: + + * Pricing and budget + * Geographic region availability + * Scaling requirements + * Additional features (backups, monitoring, etc.) + + + + ## Migrate database + + **Why it's necessary?** + + Pushing database migrations ensures that your database schema in the remote database instance is configured to match TurboStarter's requirements. This step is crucial for the application to function correctly. + + **How to do it?** + + You basically have two possibilities of doing a migration: + + + + TurboStarter comes with predefined Github Actions workflow to handle database migrations. You can find its definition in the `.github/workflows/publish-db.yml` file. + + What you need to do is to set your `DATABASE_URL` as a [secret for your Github repository](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions). + + Then, you can run the workflow which will publish the database schema to your remote database instance. + + [Check how to run Github Actions workflow.](https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/manually-running-a-workflow) + + + + You can also run your migrations locally, although this is not recommended for production. + + To do so, set the `DATABASE_URL` environment variable to your database URL (that comes from your database provider) in `.env.local` file and run the following command: + + ```bash + pnpm with-env pnpm --filter @turbostarter/db db:migrate + ``` + + This command will run the migrations and apply them to your remote database. + + [Learn more about database migrations.](/docs/web/database/migrations) + + + + + + ## Configure OAuth Providers + + **Why it's necessary?** + + Configuring OAuth providers like [Google](https://www.better-auth.com/docs/authentication/google) or [Github](https://www.better-auth.com/docs/authentication/github) ensures that users can log in using their existing accounts, enhancing user convenience and security. This step involves setting up the OAuth credentials in the provider's developer console, configuring the necessary environment variables, and setting up callback URLs to point to your production app. + + **How to do it?** + + 1. Follow the provider-specific guides to set up OAuth credentials for the providers you want to use. For example: + * [Apple OAuth setup guide](https://www.better-auth.com/docs/authentication/apple) + * [Google OAuth setup guide](https://www.better-auth.com/docs/authentication/google) + * [Github OAuth setup guide](https://www.better-auth.com/docs/authentication/github) + 2. Once you have the credentials, set the corresponding environment variables in your project. For the example providers above: + * For Apple: `APPLE_CLIENT_ID` and `APPLE_CLIENT_SECRET` + * For Google: `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` + * For Github: `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET` + 3. Ensure that the callback URLs for each provider are set to point to your production app. **This is crucial for the OAuth flow to work correctly.** + + You can add or remove OAuth providers based on your needs. Just make sure to follow the provider's setup guide, set the required environment variables, and configure the callback URLs correctly. + + + + ## Setup billing provider + + **Why it's necessary?** + + Well - you want to get paid, right? Setting up billing ensures that you can charge your users for using your SaaS application, enabling you to monetize your service and cover operational costs. + + **How to do it?** + + * Create a [Stripe](/docs/web/billing/stripe), [Lemon Squeezy](/docs/web/billing/lemon-squeezy) or [Polar](/docs/web/billing/polar) account. + * Update the environment variables with the correct values for your billing service. + * Point webhooks from Stripe, Lemon Squeezy or Polar to `/api/billing/webhook`. + * Refer to the [relevant documentation](/docs/web/billing/overview) for more details on setting up billing. + + + + ## Setup emails provider + + **Why it's necessary?** + + Setting up an email provider is crucial for your SaaS application to send notifications, confirmations, and other important messages to your users. This enhances user experience and engagement, and is a standard practice in modern web applications. + + **How to do it?** + + * Create an account with an email service provider of your choice. See [available providers](/docs/web/emails/configuration#providers) for more information. + * Update the environment variables with the correct values for your email service. + * Refer to the [relevant documentation](/docs/web/emails/overview) for more details on setting up email. + + + + ## Setup storage provider + + **Why it's necessary?** + + Don't forget to configure your storage provider, if you want to operate on files in your app. By default, this is optional — the app can run without a storage provider — but some features could be unavailable (e.g., avatar uploads and other file-related actions). + + **How to do it?** + + * Review the [Storage overview](/docs/web/storage/overview). + * Follow [Storage configuration](/docs/web/storage/configuration) to choose and set up a provider. + * Add any required environment variables in your **hosting provider**. + + + + ## Environment variables + + **Why it's necessary?** + + Setting the correct environment variables is essential for the application to function correctly. These variables include API keys, database URLs, and other configuration details required for your app to connect to various services. + + **How to do it?** + + Use our `.env.example` files to get the correct environment variables for your project. Then add them to your **hosting provider's environment variables**. Redeploy the app once you have the URL to set in the environment variables. + + + + ## Deploy web app to production + + **Why it's necessary?** + + Because your users are waiting! Deploying your Next.js app to a hosting provider makes it accessible to users worldwide, allowing them to interact with your application. + + **How to do it?** + + Deploy your Next.js app to chosen hosting provider. **Copy the deployment URL and set it as an environment variable in your project's settings.** Feel free to check out our dedicated guides for the most popular hosting providers: + + + + + + + + + + + + + + + + + + We also have a dedicated guide for [deploying your API as a standalone service](/docs/web/deployment/api). + + + +That's it! Your app is now live and accessible to your users, good job! 🎉 + + + * Update the legal pages with your company's information (privacy policy, terms of service, etc.). + * Remove the placeholder blog and documentation content / or replace it with your own. + * Customize authentication emails and other email templates. + * Update the favicon and logo with your own branding. + * Update the FAQ and other static content with your own information. + + + +--- +url: /docs/web/deployment/docker +title: Docker +description: Learn how to containerize your TurboStarter app with Docker. +--- + +[Docker](https://docker.com) is a popular platform for containerizing applications, making it easy to package your app with all its dependencies for consistent performance across environments. It simplifies development, testing, and deployment. + +This guide explains how to containerize your TurboStarter app using Docker. You'll learn to create a Dockerfile, build a container image, and run your app in a container for a reliable and portable setup. + + + + ## Configure Next.js for Docker + + First of all, we need to configure Next.js to output the build files in the [standalone format](https://nextjs.org/docs/pages/api-reference/config/next-config-js/output) - it's required for the Docker image to work. To do this, we need to add the following to our `next.config.ts` file: + + ```js title="apps/web/next.config.ts" + import type { NextConfig } from "next"; + + const config: NextConfig = { + output: "standalone", + + ... + }; + ``` + + + + ## Create a Dockerfile + + [Dockerfile](https://docs.docker.com/get-started/02_our_app/) is a text file that contains the instructions for building a [Docker image](https://docs.docker.com/get-started/02_our_app/). It defines the environment, dependencies, and commands needed to run your app. You can safely copy the following Dockerfile to your project: + + ```dockerfile title="apps/web/Dockerfile" + FROM node:22-alpine AS base + ENV PNPM_HOME="/pnpm" + ENV PATH="$PNPM_HOME:$PATH" + RUN corepack enable + + FROM base AS pruner + WORKDIR /app + RUN apk add --no-cache libc6-compat + COPY . . + RUN pnpm dlx turbo prune web --docker + + FROM base AS builder + WORKDIR /app + RUN apk add --no-cache libc6-compat + COPY --from=pruner /app/out/json/ . + COPY --from=pruner /app/out/pnpm-lock.yaml ./pnpm-lock.yaml + RUN pnpm install --frozen-lockfile --ignore-scripts --prefer-offline && pnpm store prune + ENV SKIP_ENV_VALIDATION=1 \ + NODE_ENV=production + COPY --from=pruner /app/out/full/ . + RUN pnpm dlx turbo build --filter=web + + FROM base AS runner + WORKDIR /app + RUN addgroup -g 1001 -S nodejs && \ + adduser -S web -u 1001 -G nodejs + COPY --from=builder --chown=web:nodejs /app/apps/web/.next/standalone ./ + COPY --from=builder --chown=web:nodejs /app/apps/web/.next/static ./apps/web/.next/static + COPY --from=builder --chown=web:nodejs /app/apps/web/public ./apps/web/public + USER web + EXPOSE 3000 + CMD ["node", "apps/web/server.js"] + ``` + + Feel free to check out our [self-hosting guide](/blog/self-host-your-nextjs-turborepo-app-with-docker-in-5-minutes) for more details on how each stage of the Dockerfile works. + + And that's all we need! You can now build and run your Docker image to deploy your app anywhere you want in an [isolated environment](https://docs.docker.com/get-started/workshop/04_sharing_app/). + + + + ## Run a container + + To test if everything works correctly, you can run a [container](https://www.docker.com/resources/what-container/) locally with the following commands: + + ```bash + docker build -f ./apps/web/Dockerfile . -t turbostarter + docker run -p 3000:3000 turbostarter + ``` + + Make sure to also [pass](https://docs.docker.com/reference/cli/docker/container/run/#env) all the required environment variables to the container, so your app can start without any issues. + + If everything works correctly, you should be able to access your app at [http://localhost:3000](http://localhost:3000). + + + +That's it! You can now build and deploy your app as a Docker container to any supported hosting (e.g. [Fly.io](/docs/web/deployment/fly)). + +Using Docker containers is a great way to isolate your app from the host environment, making it easier to deploy and scale. It also simplifies the workflow if you're working with a team, as you can easily share the Docker image with your colleagues and they will run the app in the **exact same** environment. + + +--- +url: /docs/web/deployment/fly +title: Fly.io +description: Learn how to deploy your TurboStarter app to Fly.io. +--- + +[Fly.io](https://fly.io) makes deploying web applications to the cloud easy and efficient. It handles scaling, monitoring, and logging so you can focus on building your app. + +This guide explains how to deploy your TurboStarter app on Fly.io. You'll learn how to leverage [Docker](/docs/web/deployment/docker) containers to deploy your app, set up builds, and manage environment variables for a smooth and reliable deployment. + + + To deploy to Fly.io, you need to have an account. You can create one [here](https://fly.io/app/sign-up). + + You also need to have [Docker](/docs/web/deployment/docker) configured in your project. + + + + + ## Setup Fly CLI + + As we will be using Fly CLI to launch and manage our app, you need to install and setup it on your machine. + + [Check the official documentation on how to install Fly CLI](https://fly.io/docs/flyctl/install/). + + After you've installed Fly CLI, you need to login to your Fly account and connect it with your machine: + + ```bash + fly auth login + ``` + + [Read more about authenticating CLI](https://fly.io/docs/flyctl/auth/#available-commands). + + Now you're ready to launch your app! + + + + ## Launch project + + Use a [Dockerfile](/docs/web/deployment/docker) to launch your app with [Fly CLI](https://fly.io/docs/flyctl/). You can use the following command to do this from your local machine: + + ```bash + fly launch --dockerfile apps/web/Dockerfile + ``` + + Make sure to set all the required configuration in the CLI steps (e.g. set port to `3000`, setup additional services, choose billing plan, etc.). + + ![Fly launch](/images/docs/web/deployment/fly/launch.png) + + + If you want to achieve better performance and lower latency in your API requests, you can customize the region of your Render service. Make sure to set it to the region closest to your database and users. + + + After the launch is complete, Fly will output your project configuration into `fly.toml` file. The configuration of your project is stored there, feel free to customize it to your needs: + + ```toml title="fly.toml" + app = 'web-aged-sky-5596' + primary_region = 'ams' + + [build] + dockerfile = 'apps/web/Dockerfile' + + [http_service] + internal_port = 3000 + force_https = true + auto_stop_machines = 'stop' + auto_start_machines = true + min_machines_running = 0 + processes = ['app'] + + [[vm]] + memory = '512mb' + cpu_kind = 'shared' + cpus = 1 + ``` + + See [Fly.io documentation](https://fly.io/docs/reference/configuration) for more information on how to use this file. + + + + ## Set up secrets + + To make your app fully functional, you need to set up required environment variables. You can do this by running the following command: + + ```bash + fly secrets set --app DATABASE_URL=... + ``` + + They will be automatically added to your app's runtime environment. + + + + ## Deploy! + + Each time you make changes to `fly.toml` or secrets, you need to re-deploy your app to apply changes to the running app. + + To do this, just run the following command in your project directory: + + ```bash + fly deploy + ``` + + This will build your app and deploy it to Fly.io with the latest code version. + + ![Fly deploy](/images/docs/web/deployment/fly/deploy.png) + + That's it! Your app is now deployed to Fly.io, congratulations! 🎉 + + + +Fly is a platform that allows you to deploy and manage applications in the cloud. It provides a simple and intuitive way to deploy your app, with features such as automatic scaling, load balancing, and rolling updates. With Fly, you can focus on building your app without worrying about the underlying infrastructure. + + +--- +url: /docs/web/deployment/netlify +title: Netlify +description: Learn how to deploy your TurboStarter app to Netlify. +--- + +[Netlify](https://netlify.com) is a powerful platform for deploying modern web applications. It offers continuous deployment, serverless functions, and a global CDN to ensure your application is fast and reliable. + +In this guide, we will walk through the steps to deploy your TurboStarter app to Netlify. You will learn how to connect your repository, configure build settings, and manage environment variables to ensure a smooth deployment process. + + + To deploy to Netlify, you need to have an account. You can create one [here](https://netlify.com/signup). + + + + + ## Create new site + + Once you've created your account and logged in, the Netlify dashboard will display an option to add a new site. Click on the *Import from Git* button to begin connecting your Git repository. + + ![Create new site](/images/docs/web/deployment/netlify/create-site.png) + + If you've already had a Netlify account, you can get to this step by clicking on the *Sites* tab in the navigation menu. + + + + ## Connect your repository + + Choose the Git provider of your project and select the repository you want to deploy. + + ![Connect repository](/images/docs/web/deployment/netlify/connect-repository.png) + + + To connect your repository, you need to authorize Netlify to access it. It's recommended to follow a *least privileged access* approach, so to only grant access to the repository you want to deploy, not the entire account. + + + + + ## Configure build settings + + Last step before deploying! Configure the build settings according to your project configuration. Use the screenshots provided below for reference to ensure a smooth deployment process. + + ![Netlify build settings](/images/docs/web/deployment/netlify/build-settings.png) + + Also, add all environment variables under *Environment variables* section - it's required to make the build process work. + + + + ## Deploy! + + Click on the *Deploy* button to start the deployment process. + + ![Netlify deploy](/images/docs/web/deployment/netlify/deploy.png) + + That's it! Your app is now deployed to Netlify, congratulations! 🎉 + + + + + If you want to achieve better performance and lower latency in your API requests, you can customize the region of your Netlify serverless functions. Make sure to set it to the region closest to your database and users. + + ![Netlify region](/images/docs/web/deployment/netlify/region.png) + + Unfortunately, it's a paid feature, so you need to upgrade your Netlify account to be able to change it. + + + +--- +url: /docs/web/deployment/railway +title: Railway +description: Learn how to deploy your TurboStarter app to Railway. +--- + +[Railway](https://railway.app) is a platform that allows you to deploy your web applications to a cloud environment. It provides a simple and efficient way to manage your application's infrastructure, including scaling, monitoring, and logging. + +This guide provides a step-by-step walkthrough for deploying your TurboStarter app on Railway, and taking advantage of its features in production environment. You'll discover how to link your repository, tailor build settings, and oversee environment variables, ensuring a smooth and optimized deployment process that leverages Railway's capabilities. + + + To deploy to Railway, you need to have an account. You can create one [here](https://railway.app/signup). + + + + + ## Create new project + + We'll use [Railway](https://railway.app) web app to deploy our project. First, let's create a new project. + + ![Railway create project](/images/docs/web/deployment/railway/create-project.png) + + Proceed with the option to *Deploy from Github repo*. + + + + ## Connect repository + + Choose the Git provider of your project and select the repository you want to deploy. + + ![Connect repository](/images/docs/web/deployment/railway/connect-repository.png) + + + If your repository is private you need to authorize Railway to access it. It's recommended to follow a *least privileged access* approach, so to only grant access to the repository you want to deploy, not the entire account. + + + + + ## Configure project settings + + Finalize your deployment by configuring the build settings to match your project's specific needs. Refer to the points below to ensure a seamless deployment process. + + ### Commands + + Configure the build and start commands to ensure that your project is built and started correctly. + + ![Railway project commands](/images/docs/web/deployment/railway/commands.png) + + Make sure to set them to the following values: + + * **Build command** - `pnpm dlx turbo build --filter=web` + * **Start command** - `pnpm --filter=web start` + + ### Environment variables + + Last, but not least, you need to set the environment variables for your project. Make sure to check if all the required variables are set. + + ![Railway environment variables](/images/docs/web/deployment/railway/environment-variables.png) + + + If you want to achieve better performance, lower latency in your API requests or add some replicas of your application, you can customize the region of your Railway instance. Make sure to set it to the region closest to your database and users. + + ![Railway region](/images/docs/web/deployment/railway/region.png) + + + You can also use a [Railway config file](https://docs.railway.com/guides/config-as-code) to manage your project's settings in one place, as a code. + + + + ## Deploy! + + Click on the *Deploy* button to start the deployment process. + + ![Railway deploy](/images/docs/web/deployment/railway/deploy.png) + + That's it! Your app is now deployed to Railway, congratulations! 🎉 + + + +Feel free to scale your deployment to multiple regions or isolate it in the separate network. Check out the [Railway documentation](https://docs.railway.app) for more information about which services are available. + + +--- +url: /docs/web/deployment/render +title: Render +description: Learn how to deploy your TurboStarter app to Render. +--- + +[Render](https://render.com) offers a unique combination of features that make it an ideal platform for deploying modern web applications. With Render, you can leverage continuous deployment, managed databases, and a global CDN to ensure your application is not only fast and reliable but also scalable and secure. + +In this guide, we will walk through the steps to deploy your TurboStarter app to Render, highlighting the benefits of using Render's platform. You will learn how to connect your repository, configure build settings, and manage environment variables to ensure a seamless and efficient deployment process that takes advantage of Render's features. + + + To deploy to Render, you need to have an account. You can create one [here](https://dashboard.render.com/register). + + + + + ## Create a new service + + Navigate to the [Render dashboard](https://dashboard.render.com) and click on the *New* button. + + ![Create new service](/images/docs/web/deployment/render/create-service.png) + + Pick the *Web Service* option and proceed to the next step. + + + + ## Connect your repository + + Choose the Git provider of your project and select the repository you want to deploy. + + ![Connect repository](/images/docs/web/deployment/render/connect-repository.png) + + + If your repository is private you need to authorize Render to access it. It's recommended to follow a *least privileged access* approach, so to only grant access to the repository you want to deploy, not the entire account. + + + + + ## Configure service settings + + Finalize your deployment by configuring the build settings to match your project's specific needs. Refer to the screenshots below to ensure a seamless deployment process. + + ![Render service settings](/images/docs/web/deployment/render/general-settings.png) + + You can also group your service with other services (e.g. [databases](https://render.com/docs/postgresql-creating-connecting) or [cron jobs](https://render.com/docs/cronjobs)) in a [Project](https://render.com/docs/projects), which will help you manage them together. + + [Read official documentation for more information](https://render.com/docs/projects). + + + If you want to achieve better performance and lower latency in your API requests, you can customize the region of your Render service. Make sure to set it to the region closest to your database and users. + + + ### Commands + + Configure the build and start commands to ensure that your project is built and started correctly. + + ![Render service commands](/images/docs/web/deployment/render/commands.png) + + Make sure to set them to the following values: + + * **Build command** - `pnpm install --frozen-lockfile; pnpm dlx turbo build --filter=web` + * **Start command** - `pnpm --filter=web start` + + ### Instance type + + Select a plan that fits your project's needs. + + ![Render instance type](/images/docs/web/deployment/render/instance-type.png) + + For testing purposes or MVPs, you can safely use the *Free* plan. Although, for the production version, it's recommended to upgrade your plan, as it offers more resources and your project won't be paused after periods of inactivity. + + ### Environment variables + + Last, but not least, you need to set the environment variables for your project. Make sure to check if all the required variables are set. + + ![Render environment variables](/images/docs/web/deployment/render/environment-variables.png) + + You can also modify *Advanced settings* to set e.g. [health checks](https://render.com/docs/deploys#health-checks) or modify [auto deploy](https://render.com/docs/deploys#automatic-git-deploys) triggers. + + + + ## Deploy! + + Click on the *Deploy Web Service* button to start the deployment process. + + ![Render deploy](/images/docs/web/deployment/render/deploy.png) + + That's it! Your app is now deployed to Render, congratulations! 🎉 + + + +Render is a powerful platform with a lot of integrations and features. Feel free to check out the [official documentation](https://render.com/docs) for more information. + + +--- +url: /docs/web/deployment/vercel +title: Vercel +description: Learn how to deploy your TurboStarter app to Vercel. +--- + +In general you can deploy the application to any hosting provider that supports Node.js, but we recommend using [Vercel](https://vercel.com) for the best experience. + +Vercel is the easiest way to deploy Next.js apps. It's the company behind Next.js and has first-class support for Next.js. + + + To deploy to Vercel, you need to have an account. You can create one [here](https://vercel.com/signup). + + +TurboStarter has two, separate ways to deploy to Vercel, each ships with **one-click deployment**. Choose the one that best fits your needs. + + + + Deploying with this method is the easiest and fastest way to get your app up and running on the cloud provider. Follow these steps: + + + + ## Connect your git repository + + After signing up you will be promted to import a git repository. Select the git provider of your project and connect your git account with Vercel. + + ![Vercel import project](/images/docs/web/deployment/vercel/connect-repository.webp) + + + + ## Configure project settings + + As we're working in monorepo, some additional settings are required to make the build process work. + + Make sure to set the following settings: + + * **Build command**: `pnpm turbo build --filter=web` - to build only the web app + * **Root directory**: `apps/web` - to make sure Vercel uses the web folder as the root directory (make sure to check *Include files outside the root directory in the Build Step* option, it will ensure that all packages from your monorepo are included in the build process) + + ![Vercel project settings](/images/docs/web/deployment/vercel/project-settings.png) + + + + + + + + + + ## Configure environment variables + + Please make sure to set all the environment variables required for the project to work correctly. You can find the list of required environment variables in the `.env.example` file in the `apps/web` directory. + + The environment variables can be set in the Vercel dashboard under *Project Settings* > *Environment Variables*. Make sure to set them for all environments (Production, Preview, and Development) as needed. + + **Failure to set the environment variables will result in the project not working correctly.** + + If the build fails, deep dive into the logs to see what is the issue. Our Zod configuration will validate and report any missing environment variables. To find out which environment variables are missing, please check the logs. + + + The first time this may fail if you don't yet have a custom domain connected since you cannot place it in the environment variables yet. It's fine. Make the first deployment fail, then pick the domain and add it. Redeploy. + + + + + ## Deploy! + + Click on the *Deploy* button to start the deployment process. + + ![Vercel deploy](/images/docs/web/deployment/vercel/success.png) + + That's it! Your app is now deployed to Vercel, congratulations! 🎉 + + + + + + Despite connecting your repository is the easiest way to deploy to Vercel, we recommend using preconfigured Github Actions for the most granular control over your deployments. + + We'll leverage [Vercel CLI](https://vercel.com/docs/cli) to deploy the application on the CI/CD pipeline. [See official documentation on deploying to Github Actions](https://vercel.com/guides/how-can-i-use-github-actions-with-vercel). + + + + ## Get Vercel Access Token + + To deploy the application, we need to get Vercel access token. + + Please, follow [this guide](https://vercel.com/guides/how-do-i-use-a-vercel-api-access-token) to create one. + + ![Vercel access token](/images/docs/web/deployment/vercel/access-token.avif) + + + + ## Install Vercel CLI + + We need to install [Vercel CLI](https://vercel.com/docs/cli) locally to be able to get required credentials for our Github Actions. + + You can install it using following command: + + ```bash + pnpm i -g vercel + ``` + + Then, login to Vercel using following command: + + ```bash + vercel login + ``` + + + + ## Get credentials + + Inside your folder, run following command to create a new project: + + ```bash + vercel link + ``` + + This will generate a `.vercel` folder, where you can find `project.json` file with `projectId` and `orgId`. + + + + ## Configure Github Actions + + Inside GitHub, add `VERCEL_TOKEN`, `VERCEL_ORG_ID`, and `VERCEL_PROJECT_ID` as [secrets](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions) to your repository. + + ![Github secrets](/images/docs/web/deployment/vercel/github-tokens.png) + + This will allow Github Actions to access your settings and deploy the application to Vercel. + + + + ## Configure project settings + + As we're working in monorepo, some additional settings are required to make the build process work. + + Make sure to set the following settings: + + * **Build command**: `pnpm turbo build --filter=web` - to build only the web app + * **Root directory**: `apps/web` - to make sure Vercel uses the web folder as the root directory (make sure to check *Include files outside the root directory in the Build Step* option, it will ensure that all packages from your monorepo are included in the build process) + + ![Vercel project settings](/images/docs/web/deployment/vercel/project-settings.png) + + + + + + + + + + ## Configure environment variables + + Please make sure to set all the environment variables required for the project to work correctly. You can find the list of required environment variables in the `.env.example` file in the `apps/web` directory. + + The environment variables can be set in the Vercel dashboard under *Project Settings* > *Environment Variables*. Make sure to set them for all environments (Production, Preview, and Development) as needed. + + **Failure to set the environment variables will result in the project not working correctly.** + + If the build fails, deep dive into the logs to see what is the issue. Our Zod configuration will validate and report any missing environment variables. To find out which environment variables are missing, please check the logs. + + + The first time this may fail if you don't yet have a custom domain connected since you cannot place it in the environment variables yet. It's fine. Make the first deployment fail, then pick the domain and add it. Redeploy. + + + + + ## Deploy! + + By default, TurboStarter comes with a Github Actions workflow that can be [triggered manually](https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/manually-running-a-workflow). + + The configuration is located in `.github/workflows/publish-web.yml`, you can easily customize it to your needs, for example to trigger a deployment from `main` branch. + + ```diff title=".github/workflows/publish-web.yml" + on: + - workflow_dispatch: + + push: + + branches: + + - main + ``` + + Then, every time you push to `main` branch, the workflow will be triggered and the application will be deployed to Vercel. + + ![Vercel deploy](/images/docs/web/deployment/vercel/success.png) + + That's it! Your app is now deployed to Vercel, congratulations! 🎉 + + + + + + + +## Troubleshooting + +In some cases, users have reported issues with the deployment to Vercel using the default parameters. If you encounter problems, try these troubleshooting steps: + +1. **Check root directory settings** + * Set the root directory to `apps/web` + * Enable *Include source files outside of the Root Directory* option + +2. **Verify build configuration** + * Ensure the framework preset is set to Next.js + * Set build command to `pnpm turbo build --filter=web` + * Set install command to `pnpm install` + +3. **Review deployment logs** + * If deployment fails, carefully review the build logs + * Look for any error messages about missing dependencies or environment variables + * Verify that all required environment variables are properly configured + +If issues persist after trying these steps, check the [deployment troubleshooting guide](/docs/web/troubleshooting/deployment) for additional help. + + +--- +url: /docs/web/emails/configuration +title: Configuration +description: Learn how to configure your emails in TurboStarter. +--- + +The `@turbostarter/email` package provides a simple and flexible way to send emails using various email providers. It abstracts the complexity of different email services and offers a consistent interface for sending emails with pre-defined templates. + +To configure the email service, you need to set an `EMAIL_FROM` environment variable. It will be used as the sender of the emails. **Please make sure that the mail address and domain are verified in your mail provider.** + +```dotenv +EMAIL_FROM="hello@resend.dev" +``` + +The email provider is configured by modifying the exports in `packages/email` package. By default, [Nodemailer](/docs/web/emails/configuration#nodemailer) is used. + +Configuration will be validated against the schema, so you will see the error messages in the console if something is not right. + +## Providers + +TurboStarter supports multiple email providers, each with its own configuration. Below, you'll find detailed information on how to set up and use each supported provider. Choose the one that best fits your needs and follow the instructions in the respective accordion section. + + + + To use Resend as your email provider, you need to [create an account](https://resend.com/) and [obtain your API key](https://resend.com/docs/dashboard/api-keys/introduction). + + Then, set it as an environment variable in your `.env.local` file in `apps/web` directory and your deployment environment: + + ```dotenv + RESEND_API_KEY="your-api-key" + ``` + + Also, make sure to activate Resend as your email provider by updating the exports in: + + + + ```ts + // [!code word:resend] + export * from "./resend"; + ``` + + + + ```ts + // [!code word:resend] + export * from "./resend/env"; + ``` + + + + To customize the provider, you can find its definition in `packages/email/src/providers/resend` directory. + + For more information, please refer to the [Resend documentation](https://resend.com/docs). + + + + To use SendGrid as your email provider, you need to [create an account](https://sendgrid.com/) and [obtain your API key](https://sendgrid.com/docs/ui/account-and-settings/api-keys/). + + Then, set it as an environment variable in your `.env.local` file in `apps/web` directory and your deployment environment: + + ```dotenv + SENDGRID_API_KEY="your-api-key" + ``` + + Also, make sure to activate SendGrid as your email provider by updating the exports in: + + + + ```ts + // [!code word:sendgrid] + export * from "./sendgrid"; + ``` + + + + ```ts + // [!code word:sendgrid] + export * from "./sendgrid/env"; + ``` + + + + To customize the provider, you can find its definition in `packages/email/src/providers/sendgrid` directory. + + For more information, please refer to the [SendGrid documentation](https://sendgrid.com/docs). + + + + To use Postmark as your email provider, you need to [create an account](https://postmarkapp.com/) and [obtain your server API token](https://postmarkapp.com/support/article/1008-what-are-the-account-and-server-api-tokens). + + Then, set it as an environment variable in your `.env.local` file in `apps/web` directory and your deployment environment: + + ```dotenv + POSTMARK_API_KEY="your-secret-api-token" + ``` + + Also, make sure to activate Postmark as your email provider by updating the exports in: + + + + ```ts + export * from "./postmark"; + ``` + + + + ```ts + // [!code word:postmark] + export * from "./postmark/env"; + ``` + + + + To customize the provider, you can find its definition in `packages/email/src/providers/postmark` directory. + + For more information, please refer to the [Postmark documentation](https://postmarkapp.com/developer). + + + + To use Plunk as your email provider, you need to [create an account](https://plunk.dev/) and [obtain your API key](https://docs.useplunk.com/api-reference/authentication). + + Then, set it as an environment variable in your `.env.local` file in `apps/web` directory and your deployment environment: + + ```dotenv + PLUNK_API_KEY="your-api-key" + ``` + + Also, make sure to activate Plunk as your email provider by updating the exports in: + + + + ```ts + // [!code word:plunk] + export * from "./plunk"; + ``` + + + + ```ts + // [!code word:plunk] + export * from "./plunk/env"; + ``` + + + + To customize the provider, you can find its definition in `packages/email/src/providers/plunk` directory. + + For more information, please refer to the [Plunk documentation](https://docs.useplunk.com). + + + + If you're using the `nodemailer` as your email provider, you'll need to set the following SMTP configuration in your environment variables: + + ```dotenv + NODEMAILER_HOST="your-smtp-host" + NODEMAILER_PORT="your-smtp-port" + NODEMAILER_USER="your-smtp-user" + NODEMAILER_PASSWORD="your-smtp-password" + ``` + + The variables are: + + * `NODEMAILER_HOST`: The host of your SMTP server. + * `NODEMAILER_PORT`: The port of your SMTP server. + * `NODEMAILER_USER`: The email address user of your SMTP server. + * `NODEMAILER_PASSWORD`: The password for the email account. + + Also, make sure to activate nodemailer as your email provider by updating the exports in: + + + + ```ts + // [!code word:nodemailer] + export * from "./nodemailer"; + ``` + + + + ```ts + // [!code word:nodemailer] + export * from "./nodemailer/env"; + ``` + + + + To customize the provider, you can find its definition in `packages/email/src/providers/nodemailer` directory. + + For more information, please refer to the [nodemailer documentation](https://nodemailer.com/smtp/). + + + +## Templates + +In the `@turbostarter/email` package, we provide a set of pre-defined templates for you to use. You can find them in the `packages/email/src/templates` directory. + +When you run your development server, you will be able to preview all available templates in the browser under [http://localhost:3005](http://localhost:3005). + +![Email preview](/images/docs/web/emails/development.png) + +Next to the templates, you can also find some shared components that you can use in your emails. The file structure looks like this: + + + + + + + + + + + +Feel free to add your own templates and components or modify existing ones to match them with your brand and style. + +### How to add a new template? + +We'll go through the process of adding a new template, as it requires a few steps to make sure everything works correctly. + + + + #### Define types + + Let's assume that we want to add a **welcome email**, that new users will receive after signing up. + + We'll start with defining new template type in `packages/email/src/types/templates.ts` file: + + ```ts title="templates.ts" + export const EmailTemplate = { + ...AuthEmailTemplate, + WELCOME: "welcome", + } as const; + ``` + + Also, we would need to add types for variables that we'll pass to the template (if any), in our case it will be just a `name` of the user: + + ```ts title="templates.ts" + type WelcomeEmailVariables = { + welcome: { + name: string; + }; + }; + + export type EmailVariables = AuthEmailVariables | WelcomeEmailVariables; + ``` + + By doing this, we ensure that payload passed to the template will have all required properties and we won't end up with an email that tells your user "Hey, undefined!". + + + + #### Create template + + Next up, we need to create a file with the template itself. We'll create an `welcome.tsx` file in `packages/email/src/templates` directory. + + ```tsx title="welcome.tsx" + import { Heading, Preview, Text } from "@react-email/components"; + + import { Button } from "../_components/button"; + import { Layout } from "../_components/layout/layout"; + + import type { EmailTemplate, EmailVariables } from "../../types"; + + type Props = EmailVariables[typeof EmailTemplate.WELCOME]; + + export const Welcome = ({ name }: Props) => { + return ( + + Welcome to TurboStarter! + Hi, {name}! + + Start your journey with our app by clicking the button below. + + + + ); + }; + + Welcome.subject = "Welcome to TurboStarter!"; + + Welcome.PreviewProps = { + name: "John Doe", + }; + + export default Welcome; + ``` + + As you can see, by defining appropriate types for the template, we can safely use the variables as a props in the template. + + To learn more about supported components, please refer to the [React Email documentation](https://react.email/docs/components). + + + + #### Register template + + We have to register the template in the main entrypoint of the templates in `packages/email/src/templates/index.ts` file: + + ```ts title="index.ts" + import { Welcome } from "./welcome"; + + export const templates = { + ... + [EmailTemplate.WELCOME]: Welcome, + } as const; + ``` + + That way, it will be available in the `sendEmail` function, enabling us to send it from the server-side of your application. + + ```ts + import { sendEmail } from "@turbostarter/email/server"; + + sendEmail({ + to: "user@example.com", + template: EmailTemplate.WELCOME, + variables: { + name: "John Doe", + }, + }); + ``` + + Learn more about sending emails in the [dedicated section](/docs/web/emails/sending). + + + +Et voilà! You've just added a new email template to your application 🎉 + +### Translating templates + +You can also translate your templates to support multiple languages. Each mail template is passed the `locale` property, which you can use to get the translation for the current locale. This allows you to maintain consistent translations across your application and emails. + +The translation system [uses the same i18n setup](/docs/web/internationalization/overview) as your main application, so you can reuse your existing translation files and namespaces. The translations are loaded server-side when the email is generated, ensuring the correct language is used based on the user's preferences. + +Here's how you can implement translations in your email templates: + +```tsx +import { Heading, Preview, Text } from "@react-email/components"; + +import { getTranslation } from "@turbostarter/i18n/server"; + +import { Button } from "../_components/button"; +import { Layout } from "../_components/layout/layout"; + +import type { + EmailTemplate, + EmailVariables, + CommonEmailProps, +} from "../../types"; + +type Props = EmailVariables[typeof EmailTemplate.WELCOME] & CommonEmailProps; + +export const Welcome = async ({ name, locale }: Props) => { + const { t } = await getTranslation({ locale, ns: "auth" }); + + return ( + + {t("account.welcome.preview")} + {t("account.welcome.heading", { name })} + + {t("account.welcome.body")} + + + + ); +}; + +Welcome.subject = async ({ locale }: CommonEmailProps) => { + const { t } = await getTranslation({ locale, ns: "auth" }); + return t("account.welcome.subject"); +}; + +Welcome.PreviewProps = { + name: "John Doe", + locale: "en", +}; + +export default Welcome; +``` + +To send the email in the specified language, you can pass the optional `locale` argument to the `sendEmail` function: + +```ts +sendEmail({ + to: "user@example.com", + template: EmailTemplate.WELCOME, + variables: { + name: "John Doe", + }, + locale: "en", // [!code highlight] +}); +``` + +Learn more about translations in the [dedicated section](/docs/web/internationalization/translations). + + +--- +url: /docs/web/emails/overview +title: Overview +description: Get started with emails in TurboStarter. +--- + +For mailing functionality, TurboStarter integrates [React Email](https://react.email/docs/introduction) which enables you to build your emails from composable React components. + + + It's a simple, yet powerful library that allows you to **write your emails in React**. + + It also allows you to use **Tailwind CSS for styling**, which is a huge advantage, as we can share almost everything from the main app with the emails package, keeping them consistent with rest of the app. + + +You can read more about `react-email` package in the [official documentation](https://react.email/docs/introduction). + +## Providers + +TurboStarter implements multiple providers for managing and sending emails. To learn more about each provider and how to configure them, see the respective section: + + + + + + + + + + + + + +All configuration and setup is built-in with a unified API, so you can switch between providers by simply changing the exports and even introduce your own provider without breaking any sending-related logic. + +## Development + +When you [setup your development environment](/docs/web/installation/development) and run `pnpm dev` command a new app will start at [http://localhost:3005](http://localhost:3005). + +![Email preview](/images/docs/web/emails/development.png) + +There you'll be able to check your email templates and send test emails from your app. It includes hot-reloading, so when you make change in the code - it will be reflected in the browser. + +Learn more about configuration and setup of the emails in TurboStarter in the following sections. + + +--- +url: /docs/web/emails/sending +title: Sending emails +description: Learn how to send emails in TurboStarter. +--- + +The strategy for sending emails, that every provider has to implement, is **extremely simple**: + +```ts +export interface EmailProviderStrategy { + send: (args: { + to: string; + subject: string; + text: string; + html?: string; + }) => Promise; +} +``` + + + You don't need to worry much about it, as all the providers are already configured for you. Just be aware of it if you want to add your custom provider. + + +Then, we define a general `sendEmail` function that you can use as an API for sending emails in your app: + +```ts +const sendEmail = async ({ + to, + template, + variables, + locale, +}: { + to: string; + template: T; + variables: EmailVariables[T]; + locale?: string; +}) => { + const { html, text, subject } = await getTemplate({ + id: template, + variables, + locale, + }); + + return send({ to, subject, html, text }); +}; +``` + +The arguments are: + +* `to`: The recipient's email address. +* `template`: The email template to use. +* `variables`: The variables to pass to the template. +* `locale`: The locale to use for the email. + +It returns a promise that resolves when the email is sent successfully. If there is an error, the promise will be rejected with an error message. + +To send an email, just invoke the `sendEmail` with the correct arguments from the **server-side** of your application: + +```ts +import { sendEmail } from "@turbostarter/email/server"; + +sendEmail({ + to: "user@example.com", + template: EmailTemplate.WELCOME, + variables: { + name: "John Doe", + }, + locale: "en", +}); +``` + +And that's it! You're ready to send emails in your application 🚀 + +## Authentication emails + +TurboStarter comes with a set of pre-configured authentication emails for various purposes, including magic links and password reset functionality. + +To handle the sending of these emails at the right time, we use [Better Auth Hooks](https://www.better-auth.com/docs/concepts/email), which trigger when specific authentication events occur. + +The logic for determining which email to send is already implemented for you in the `packages/auth/src/server.ts` file, alongside your [authentication configuration](/docs/web/auth/configuration): + +```ts title="server.ts" +export const auth = betterAuth({ + emailAndPassword: { + enabled: true, + sendResetPassword: async ({ user, url }) => + sendEmail({ + to: user.email, + template: EmailTemplate.RESET_PASSWORD, + variables: { + url, + }, + }), + }, + emailVerification: { + sendVerificationEmail: async ({ user, url }) => + sendEmail({ + to: user.email, + template: EmailTemplate.CONFIRM_EMAIL, + variables: { + url, + }, + }), + }, + + /* other options */ +}); +``` + +As you can see, the authentication emails are automatically sent when needed (e.g. when user requests password reset or needs to verify their email address). + +You can customize authentication templates by modifying them in the `packages/email/src/templates` directory, or create your own templates for other use cases in your application. + + +--- +url: /docs/web/extras +title: Extras +description: See what you get together with the code. +--- + +## Tips and Tricks + +In many places, next to the code you will find some marketing tips, design suggestions, and potential risks. This is to help you build a better product and avoid common pitfalls. + +```tsx title="Hero.tsx" +return ( +
+ {/* 💡 Use something that user can visualize e.g. + "Ship your startup while on the toilet" */} +

Best startup on the world

+
+); +``` + +### Submission tips + +When it comes to mobile app and browser extension, you must submit your product to review from Apple/Google etc. We have some tips for you to make sure your submission goes smoothly. + +```json title="app.json" +{ + "ios": { + "infoPlist": { + /* 🍎 add descriptive justification of using this permission on iOS */ + "NSCameraUsageDescription": "This app uses the camera to scan barcodes on event tickets." + } + } +} +``` + +As well as providing you with the info on how to make your store listings better: + +```json title="package.json" +{ + "manifest": { + /* 💡 Use localized messages to get more visibility in web stores */ + "name": "__MSG_extensionName__", + "default_locale": "en" + } +} +``` + +## Discord community + +We have a Discord community where you can ask questions and share your projects. It's a great place to get help and meet other developers. Check more details at [/discord](/discord). + + + +![Discord](/images/docs/discord.png) + +## 25+ SaaS Ideas + +Not sure what to build? We have a list of **25+** SaaS ideas that you can use to get started 🔥 + +Grouped by category, these ideas are a great way to get inspired and start building your next project. + +Including design, copies, marketing tips and potential risks, this list is a great resource for anyone looking to build a SaaS product. + +![SaaS Ideas](/images/docs/saas-ideas.png) + + +--- +url: /docs/web/faq +title: FAQ +description: Find answers to common technical questions. +--- + +## Why isn't everything hidden and configured with one BIG config file? + +TurboStarter intentionally exposes the underlying code rather than hiding it behind configuration files (like some starters do). This design choice follows our **you own your code** philosophy, giving you full control and flexibility over your codebase. + +While a single config file might seem simpler initially, it often becomes restrictive when you need to customize functionality beyond what the config allows. With direct access to the code, you can modify any part of the system to match your specific requirements. + +## I don't know some technology! Should I buy TurboStarter? + +You should be prepared for a learning curve or consider learning it first. However, TurboStarter will still work for you if you're willing to learn. + +Even without knowing some technologies, you can still use the rest of the features. + +## I don't need mobile app or browser extension, what should I do? + +You can simply ignore the mobile app and browser extension parts of the project. You can remove the `apps/mobile` and `apps/extension` directories from the project. + +The modular nature of TurboStarter allows you to remove parts of the project that you don't need without affecting the rest of the stack. + +## I want to use a different provider for X + +Sure! TurboStarter is designed to be modular, so configuring new provider (e.g. for emails, billing or any other service) is straightforward. You just need to make sure your configuration is compatible with common interface to be able to plug it into the codebase. + +## Will you add more packages in the future? + +Yes, we will keep updating TurboStarter with new packages and features. This kit is designed to be modular, allowing for new features and packages to be added without interfering with your existing code. You can always [update your project](/docs/web/installation/update) to the latest version. + +## Can I use this kit for a non-SaaS project? + +This kit is mainly designed for SaaS projects. If you're building something other than a SaaS, the Next.js SaaS Boilerplate might include features you don't need. You can still use it for non-SaaS projects, but you may need to remove or modify features that are specific to SaaS use cases. + +## Can I use personal accounts only? + +Yes! You can disable team accounts and have personal accounts only by setting a feature flag. + +## Does it set up the production instance for me? + +No, TurboStarter does not set up the production instance for you. This includes setting up databases, Stripe, or any other services you need. TurboStarter does not have access to your Stripe or Resend accounts, so setup on your end is required. TurboStarter provides the codebase and documentation to help you set up your SaaS project. + +## Does the starter include Solito? + +No. Solito will not be included in this repo. It is a great tool if you want to share code between your Next.js and Expo app. However, the main purpose of this repo is not the integration between Next.js and Expo — it's the code splitting of your SaaS platforms into a monorepo. You can utilize the monorepo with multiple apps, and it can be any app such as Vite, Electron, etc. + +Integrating Solito into this repo isn't hard, and there are a few [official templates](https://github.com/nandorojo/solito/tree/master/example-monorepos) by the creators of Solito that you can use as a reference. + +## Does this pattern leak backend code to my client applications? + +No, it does not. The `api` package should only be a production dependency in the Next.js application where it's served. The Expo app, browser extension, and all other apps you may add in the future should only add the `api` package as a dev dependency. This lets you have full type safety in your client applications while keeping your backend code safe. + +If you need to share runtime code between the client and server, you can create a separate `shared` package for this and import it on both sides. + +## How do I get support if I encounter issues? + +For support, you can: + +1. Visit our [Discord](https://discord.gg/KjpK2uk3JP) +2. Contact us via support email ([hello@turbostarter.dev](mailto:hello@turbostarter.dev)) + +## Are there any example projects or demos? + +Yes - feel free to check out our demo app at [demo.turbostarter.dev](https://demo.turbostarter.dev). Also, you can get inspired by projects built by our customers - take a look at [Showcase](/#showcase). + +## How do I deploy my application? + +Please check the [production checklist](/docs/web/deployment/checklist) for more information. + +## How do I update my project when a new version of the boilerplate is released? + +Please read the [documentation for updating your TurboStarter code](/docs/web/installation/update). + +## Can I use the React package X with this kit? + +Yes, you can use any React package with this kit. The kit is based on React, so you are generally only constrained by the underlying technologies and not by the kit itself. Since you own and can edit all the code, you can adapt the kit to your needs. However, if there are limitations with the underlying technology, you might need to work around them. + +## Can I integrate TurboStarter into an existing project? + +TurboStarter is a full-stack starter intended to be used as the foundation of your app. You can copy individual modules or patterns into an existing codebase, but retrofitting the entire starter into a mature project is typically not recommended and is not officially supported. If you choose to copy parts, prefer isolating boundaries (e.g., `packages/` modules) and aligning interfaces first. + +## Where can I deploy my application? + +TurboStarter targets modern Node.js/Next.js runtimes. You can deploy to providers that support these environments, such as [Vercel](/docs/web/deployment/vercel), [Railway](/docs/web/deployment/railway), [Render](/docs/web/deployment/render), [Fly](/docs/web/deployment/fly), or [Netlify](/docs/web/deployment/netlify) - following their Next.js guidance. Review our [production checklist](/docs/web/deployment/checklist) before going live. + +## Can I easily swap providers (billing, email, etc.)? + +Yes. The starter organizes integrations behind clear interfaces so you can replace providers (e.g., billing or email) with minimal surface changes. Keep your implementation behind a module boundary and adapt to the existing types to avoid ripple effects. + + +--- +url: /docs/web +title: Introduction +description: Get started with TurboStarter web kit. +--- + +Welcome to the TurboStarter documentation. This is your starting point for learning about the starter kit, its structure, features, and how to use it for your app development. + + + +## What is TurboStarter? + +TurboStarter is a fullstack starter kit that helps you build scalable and production-ready web apps, mobile apps, and browser extensions in minutes. + +Looking to bootstrap your project quickly? Check out our [TurboStarter CLI guide](/blog/the-only-turbo-cli-you-need-to-start-your-next-project-in-seconds) to get started in seconds. + +## Demo apps + +TurboStarter provides a suite of live demo applications you can try instantly - right in your browser, on your phone, or via browser extensions. Try them live by clicking the buttons below. + + + +## Principles + +TurboStarter is built with the following principles: + +* **As simple as possible** - It should be easy to understand, easy to use, and strongly avoid overengineering things. +* **As few dependencies as possible** - It should have as few dependencies as possible to allow you to take full control over every part of the project. +* **As performant as possible** - It should be fast and light without any unnecessary overhead. + +## Features + +Before diving into the technical details, let's overview the features TurboStarter provides. + +### Multi-platform development + +* [Web](/docs/web/stack): Build web apps with React, Next.js, and Tailwind CSS. +* [Mobile](/docs/mobile/stack): Build mobile apps with React Native and Expo. +* [Browser extension](/docs/extension/stack): Build browser extensions with React and WXT. + +For those interested in AI development, check out our dedicated [TurboStarter AI documentation](/ai/docs) with specialized features for building AI-powered applications. + + + Most features are available on all platforms. You can use the **same codebase** to build web, mobile, and browser extension apps. + + +### Authentication + + + + + + + + + + + + + + + + + + + +### Organizations/teams + + + + + + + + + + + +### Billing + + + + + + + + + + + + + + + +### Database + + + + + + + + + + + +### API + + + + + + + + + + + + + +### Admin + + + + + + + + + + + +### AI + + + + Seamless integration of OpenAI, Anthropic, Groq, Mistral, and Gemini. For more advanced AI features, check out [TurboStarter AI](/ai/docs). + + + + + + + + + +### Internationalization + + + + + + + + + + + +### Emails + + + + + + + + + + + +### Landing page + + + + + + + + + + + + + + + +### Marketing + + + + + + + + + + + + + + + + + +### Storage + + + + + + + +### CMS + + + + + + + +### Theming + + + + + + + + + + + +### Analytics + + + + + + + + + + + +### Monitoring + + + + + + + + + + + +### Deployment + + + + + + + + + + + +### Testing + + + + + + + + + + + +## Use like LEGO blocks + +The biggest advantage of TurboStarter is its modularity. You can use the entire stack or just the parts you need. It's like LEGO blocks - you can build anything you want with it. + +If you don't need a specific feature, feel free to remove it without affecting the rest of the stack. + +This approach allows for: + +* **Easy feature integration** - plug new features into the kit with minimal changes. +* **Simplified maintenance** - keep the codebase clean and maintainable. +* **Core feature separation** - distinguish between core features and custom features. +* **Additional modules** - easily add modules like billing, CMS, monitoring, logger, mailer, and more. + +## Scope of this documentation + +While building a SaaS application involves many moving parts, this documentation focuses specifically on TurboStarter. For in-depth information on the underlying technologies, please refer to their respective official documentation. + +This documentation will guide you through configuring, running, and deploying the kit, and will provide helpful links to the official documentation of technologies where necessary. + +## `llms.txt` + +You can access the entire TurboStarter documentation in Markdown format at [/llms.txt](/llms.txt). This can be used to ask any LLM (assuming it has a big enough context window) questions about the TurboStarter based on the most up-to-date documentation. + +### Example usage + +For instance, to prompt an LLM with questions about the TurboStarter: + +1. Copy the documentation contents from [/llms.txt](/llms.txt) +2. Use the following prompt format: + +``` +Documentation: +{paste documentation here} +--- +Based on the above documentation, answer the following: +{your question} +``` + +## Enjoy! + +This documentation is designed to be easy to follow and understand. If you have any questions or need help, feel free to reach out to us at [hello@turbostarter.dev](mailto:hello@turbostarter.dev). + +Explore new features, build amazing apps, and have fun! 🚀 + + +--- +url: /docs/web/installation/clone +title: Cloning repository +description: Get the code to your local machine and start developing. +--- + + + Ensure you have Git installed on your local machine before proceeding. You can download Git from [here](https://git-scm.com). + + +## Git clone + +Clone the repository using the following command: + +```bash +git clone git@github.com:turbostarter/core +``` + +By default, we're using [SSH](https://docs.github.com/en/authentication/connecting-to-github-with-ssh) for all Git commands. If you don't have it configured, please refer to the [official documentation](https://docs.github.com/en/authentication/connecting-to-github-with-ssh) to set it up. + +Alternatively, you can use HTTPS to clone the repository: + +```bash +git clone https://github.com/turbostarter/core +``` + +Another alternative could be to use the [Github CLI](https://cli.github.com/) or [Github Desktop](https://desktop.github.com/) for Git operations. + + + +## Git remote + +After cloning the repository, remove the original origin remote: + +```bash +git remote rm origin +``` + +Add the upstream remote pointing to the original repository to pull updates: + +```bash +git remote add upstream git@github.com:turbostarter/core +``` + +Once you have your own repository set up, add your repository as the origin: + +```bash +git remote add origin +``` + + + +## Staying up to date + +To pull updates from the upstream repository, run the following command daily (preferably with your morning coffee ☕): + +```bash +git pull upstream main +``` + +This ensures your repository stays up to date with the latest changes. + +Check [Updating codebase](/docs/web/installation/update) for more details on updating your codebase. + + +--- +url: /docs/web/installation/commands +title: Common commands +description: Learn about common commands you need to know to work with the project. +--- + + + For sure, you don't need these commands to kickstart your project, but it's useful to know they exist for when you need them. + + + + You can set up aliases for these commands in your shell configuration file. For example, you can set up an alias for `pnpm` to `p`: + + ```bash title="~/.bashrc" + alias p='pnpm' + ``` + + Or, if you're using [Zsh](https://ohmyz.sh/), you can add the alias to `~/.zshrc`: + + ```bash title="~/.zshrc" + alias p='pnpm' + ``` + + Then run `source ~/.bashrc` or `source ~/.zshrc` to apply the changes. + + You can now use `p` instead of `pnpm` in your terminal. For example, `p i` instead of `pnpm install`. + + + + To inject environment variables into the command you run, prefix it with `with-env`: + + ```bash + pnpm with-env + ``` + + For example, `pnpm with-env pnpm build` will run `pnpm build` with the environment variables injected. + + Some commands, like `pnpm dev`, automatically inject the environment variables for you. + + +## Installing dependencies + +To install the dependencies, run: + +```bash +pnpm install +``` + +## Starting development server + +Start development server by running: + +```bash +pnpm dev +``` + +## Building project + +To build the project (including all apps and packages), run: + +```bash +pnpm build +``` + +## Building specific app/package + +To build a specific app/package, run: + +```bash +pnpm turbo build --filter= +``` + +## Cleaning project + +To clean the project, run: + +```bash +pnpm clean +``` + +Then, reinstall the dependencies: + +```bash +pnpm install +``` + +## Formatting code + +To check for formatting errors using Prettier, run: + +```bash +pnpm format +``` + +To fix formatting errors using Prettier, run: + +```bash +pnpm format:fix +``` + +## Linting code + +To check for linting errors using ESLint, run: + +```bash +pnpm lint +``` + +To fix linting errors using ESLint, run: + +```bash +pnpm lint:fix +``` + +## Typechecking + +To typecheck the code using TypeScript for any type errors, run: + +```bash +pnpm typecheck +``` + +## Adding UI components + + + + To add a new web component, run: + + ```bash + pnpm --filter @turbostarter/ui-web ui:add + ``` + + This command will add and export a new component to `@turbostarter/ui-web` package. + + + + To add a new mobile component, run: + + ```bash + pnpm --filter @turbostarter/ui-mobile ui:add + ``` + + This command will add and export a new component to `@turbostarter/ui-mobile` package. + + + +## Services commands + + + To run the services containers locally, you need to have [Docker](https://www.docker.com/) installed on your machine. + + You can always use the cloud-hosted solution (e.g. [Neon](https://neon.tech/), [Turso](https://turso.tech/) for database) for your projects. + + +We have a few commands to help you manage the services containers (for local development). + +### Starting containers + +To start the services containers, run: + +```bash +pnpm services:start +``` + +It will run all the services containers. You can check their configs in `docker-compose.yml`. + +### Setting up services + +To setup all the services, run: + +```bash +pnpm services:setup +``` + +It will start all the services containers and run necessary setup steps. + +### Stopping containers + +To stop the services containers, run: + +```bash +pnpm services:stop +``` + +### Displaying status + +To check the status and logs of the services containers, run: + +```bash +pnpm services:status +``` + +### Displaying logs + +To display the logs of the services containers, run: + +```bash +pnpm services:logs +``` + +### Database commands + +We have a few commands to help you manage the database leveraging [Drizzle CLI](https://orm.drizzle.team/kit-docs/commands). + +#### Generating migrations + +To generate a new migration, run: + +```bash +pnpm with-env turbo db:generate +``` + +It will create a new migration `.sql` file in the `packages/db/migrations` folder. + +#### Running migrations + +To run the migrations against the db, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:migrate +``` + +It will apply all the pending migrations. + +#### Pushing changes directly + + + Make sure you know what you're doing before pushing changes directly to the db. + + +To push changes directly to the db, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:push +``` + +It lets you push your schema changes directly to the database and omit managing SQL migration files. + +#### Checking database status + +To check the status of the database, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:status +``` + +It will display the status of the applied migrations and the pending ones. + +```bash +Applied migrations: +- 0000_cooing_vargas +- 0001_curious_wallflower +- 0002_good_vertigo +- 0003_peaceful_devos +- 0004_fat_mad_thinker +- 0005_yummy_bucky +- 0006_glorious_vargas + +Pending migrations: +- 0007_nebulous_havok +``` + +#### Resetting database + +To reset the database, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:reset +``` + +It will reset the database to the initial state. + +#### Seeding database + +To seed the database with some example data (for development purposes), run: + +```bash +pnpm with-env turbo db:seed +``` + +It will populate your database with some example data. + +#### Checking database + +To check the database schema consistency, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:check +``` + +#### Studying database + +To study the database schema in the browser, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:studio +``` + +This will start the Studio on [https://local.drizzle.studio](https://local.drizzle.studio). + +## Tests commands + +### Running tests + +To run the tests, run: + +```bash +pnpm test +``` + +This will run all the tests in the project using Turbo tasks. As it leverages Turbo caching, it's [recommended](/docs/web/tests/unit#configuration) to run it in your CI/CD pipeline. + +### Running tests projects + +To run tests for all Vitest [Test Projects](https://vitest.dev/guide/projects), run: + +```bash +pnpm test:projects +``` + +This will run all the tests in the project using Vitest. + +### Watching tests + +To watch the tests, run: + +```bash +pnpm test:projects:watch +``` + +This will watch the tests for all [Test Projects](https://vitest.dev/guide/projects) and run them automatically when you make changes. + +### Generating code coverage + +To generate code coverage report, run: + +```bash +pnpm turbo test:coverage +``` + +This will generate a code coverage report in the `coverage` directory under `tooling/vitest` package. + +### Viewing code coverage + +To preview the code coverage report in the browser, run: + +```bash +pnpm turbo test:coverage:view +``` + +This will launch the report's `.html` file in your default browser. + + +--- +url: /docs/web/installation/conventions +title: Conventions +description: Some standard conventions used across TurboStarter codebase. +--- + +You're not required to follow these conventions: they're simply a standard set of practices used in the core kit. If you like them - we encourage you to keep these during your usage of the kit - so to have consistent code style that you and your teammates understand. + +## Turborepo Packages + +In this project, we use [Turborepo packages](https://turbo.build/repo/docs/core-concepts/internal-packages) to define reusable code that can be shared across multiple applications. + +* **Apps** are used to define the main application, including routing, layout, and global styles. +* **Packages** shares reusable code add functionalities across multiple applications. They're configurable from the main application. + + + **Recommendation:** Do not create a package for your app code unless you plan to reuse it across multiple applications or are experienced in writing library code. + + If your application is not intended for reuse, keep all code in the app folder. This approach saves time and reduces complexity, both of which are beneficial for fast shipping. + + **Experienced developers:** If you have the experience, feel free to create packages as needed. + + +## Imports and Paths + +When importing modules from packages or apps, use the following conventions: + +* **From a package:** Use `@turbostarter/package-name` (e.g., `@turbostarter/ui`, `@turbostarter/api`, etc.). +* **From an app:** Use `~/` (e.g., `~/components`, `~/config`, etc.). + +## Enforcing conventions + +* [Prettier](https://prettier.io/) is used to enforce code formatting. +* [ESLint](https://eslint.org/) is used to enforce code quality and best practices. +* [TypeScript](https://www.typescriptlang.org/) is used to enforce type safety. + + + + + + + + + +## Code health + +TurboStarter provides a set of tools to ensure code health and quality in your project. + +### Github Actions + +By default, TurboStarter sets up Github Actions to run tests on every push to the repository. You can find the Github Actions configuration in the `.github/workflows` directory. + +The workflow has multiple stages: + +* `format` - runs Prettier to format the code. +* `lint` - runs ESLint to check for linting errors. +* `typecheck` - runs TypeScript to check for type errors. + +### Git hooks + +Together with TurboStarter we have set up a `commit-msg` hook which will check if your commit message follows the [conventional commit](https://www.conventionalcommits.org/en/v1.0.0/) message format. This is important for generating changelogs and keeping a clean commit history. + +Although we didn't ship any pre-commit hooks (we believe in shipping fast with moving checking code responsibility to CI), you can easily add them by using [Husky](https://typicode.github.io/husky/#/). + +#### Setting up the Pre-Commit Hook + +To do so, create a `pre-commit` file in the `./..husky` directory with the following content: + +```bash +#!/bin/sh + +pnpm typecheck +pnpm lint +``` + +Turborepo will execute the commands for all the affected packages - while skipping the ones that are not affected. + +#### Make the Pre-Commit Hook Executable + +```bash +chmod +x ./.husky/pre-commit +``` + +To test the pre-commit hook, try to commit a file with linting errors or type errors. The commit should fail, and you should see the error messages in the console. + + +--- +url: /docs/web/installation/dependencies +title: Managing dependencies +description: Learn how to manage dependencies in your project. +--- + +As the package manager we chose [pnpm](https://pnpm.io/). + + + It is a fast, disk space efficient package manager that uses hard links and symlinks to save one version of a module only ever once on a disk. It also has a great [monorepo support](https://pnpm.io/workspaces). Of course, you can change it to use [Bun](https://bunpkg.com), [yarn](https://yarnpkg.com) or [npm](https://www.npmjs.com) with minimal effort. + + +## Install dependency + +To install a package you need to decide whether you want to install it to the root of the monorepo or to a specific workspace. Installing it to the root makes it available to all packages, while installing it to a specific workspace makes it available only to that workspace. + +To install a package globally, run: + +```bash +pnpm add -w +``` + +To install a package to a specific workspace, run: + +```bash +pnpm add --filter +``` + +For example: + +```bash +pnpm add --filter @turbostarter/ui motion +``` + +It will install `motion` to the `@turbostarter/ui` workspace. + +## Remove dependency + +Removing a package is the same as installing but with the `remove` command. + +To remove a package globally, run: + +```bash +pnpm remove -w +``` + +To remove a package from a specific workspace, run: + +```bash +pnpm remove --filter +``` + +## Update a package + +Updating is a bit easier since there is a nice way to update a package in all workspaces at once: + +```bash +pnpm update -r +``` + + + When you update a package, pnpm will respect the [semantic versioning](https://docs.npmjs.com/about-semantic-versioning) rules defined in the `package.json` file. If you want to update a package to the latest version, you can use the `--latest` flag. + + +## Renovate bot + +By default, TurboStarter comes with [Renovate](https://www.npmjs.com/package/renovate) enabled. It is a tool that helps you manage your dependencies by automatically creating pull requests to update your dependencies to the latest versions. You can find its configuration in the `.github/renovate.json` file. Learn more about it in the [official docs](https://docs.renovatebot.com/configuration-options/). + +When it creates a pull request, it is treated as a normal PR, so all tests and preview deployments will run. **It is recommended to always preview and test the changes in the staging environment before merging the PR to the main branch to avoid breaking the application.** + + + + +--- +url: /docs/web/installation/development +title: Development +description: Get started with the code and develop your SaaS. +--- + +## Prerequisites + +To get started with TurboStarter, ensure you have the following installed and set up: + +* [Node.js](https://nodejs.org/en) (22.x or higher) +* [Docker](https://www.docker.com) (only if you want to use local services e.g. database) +* [pnpm](https://pnpm.io) + +## Project development + + + + ### Install dependencies + + Install the project dependencies by running the following command: + + ```bash + pnpm i + ``` + + + It is a fast, disk space efficient package manager that uses hard links and symlinks to save one version of a module only ever once on a disk. It also has a great [monorepo support](https://pnpm.io/workspaces). Of course, you can change it to use [Bun](https://bunpkg.com), [yarn](https://yarnpkg.com) or [npm](https://www.npmjs.com) with minimal effort. + + + + + ### Setup environment variables + + Create a `.env.local` files from `.env.example` files and fill in the required environment variables. + + You can use the following command to recursively copy the `.env.example` files to the `.env.local` files: + + + + ```bash + find . -name ".env.example" -exec sh -c 'cp "$1" "${1%.example}.local"' _ {} \; + ``` + + + + ```bash + Get-ChildItem -Recurse -Filter ".env.example" | ForEach-Object { + Copy-Item $_.FullName ($\_.FullName -replace '\.example$', '.local') + } + ``` + + + + Check [Environment variables](/docs/web/configuration/environment-variables) for more details on setting up environment variables. + + + + ### Setup services + + If you want to use local services like [database](/docs/web/database/overview) (**recommended for development purposes**), ensure Docker is running, then setup them with: + + ```bash + pnpm services:setup + ``` + + This command initiates the containers and runs necessary setup steps, ensuring your services are up to date and ready to use. + + + + ### Start development server + + To start the application development server, run: + + ```bash + pnpm dev + ``` + + Your app should now be up and running at [http://localhost:3000](http://localhost:3000) 🎉 + + + + ### Deploy to Production + + When you're ready to deploy the project to production, follow the [checklist](/docs/web/deployment/checklist) to ensure everything is set up correctly. + + + + +--- +url: /docs/web/installation/editor-setup +title: Editor setup +description: Learn how to set up your editor for the fastest development experience. +--- + +Of course you can use every IDE you like, but you will have the best possible developer experience with this starter kit when using **VSCode-based** editor with the suggested settings and extensions. + +## Settings + +We have included most recommended settings in the `.vscode/settings.json` file to make your development experience as smooth as possible. It include mostly configs for tools like Prettier, ESLint and Tailwind which are used to enforce some conventions across the codebase. You can adjust them to your needs. + +## LLM rules + +We exposed a special endpoint that will scan all the docs and return the content as a text file which you can use to train your LLM or put in a prompt. You can find it at [/llms.txt](/llms.txt). + +The repository also includes a custom rules for most popular AI editors and agents to ensure that AI completions are working as expected and following our conventions. + +### AGENTS.md + +We've integrated specific rules that help maintain code quality and ensure AI-assisted completions align with our project standards. + +You can find them in the `AGENTS.md` file at the root of the project. This format is a standardized way to instruct AI agents to follow project conventions when generating code and it's used by over **20,000** open-source projects - including [Cursor](https://cursor.sh/), [Aider](https://aider.chat/), [Codex from OpenAI](https://openai.com/blog/openai-codex/), [Jules from Google](https://jules.ai/), [Windsurf](https://windsurf.dev/), and many others. + +```md title="AGENTS.md" +### Code Style and Structure + +- Write concise, technical TypeScript code with accurate examples +- Use functional and declarative programming patterns; avoid classes +- Prefer iteration and modularization over code duplication + +### Naming Conventions + +.... +``` + +To learn more about `AGENTS.md` rules check out the [official documentation](https://agents.md/). + +## Extensions + +Once you cloned the project and opened it in VSCode you should be promted to install suggested extensions which are defined in the `.vscode/extensions.json` automatically. In case you rather want to install them manually you can do so at any time later. + +These are the extensions we recommend: + +### ESLint + +Global extension for static code analysis. It will help you to find and fix problems in your JavaScript code. + + + +### Prettier + +Global extension for code formatting. It will help you to keep your code clean and consistent. + + + +### Pretty TypeScript Errors + +Improves TypeScript error messages shown in the editor. + + + +### Tailwind CSS IntelliSense + +Adds IntelliSense for Tailwind CSS classes to enable autocompletion and linting. + + + + +--- +url: /docs/web/installation/structure +title: Project structure +description: Learn about the project structure and how to navigate it. +--- + +The main directories in the project are: + +* `apps` - the location of the main apps +* `packages` - the location of the shared code and the API + +### `apps` Directory + +This is where the apps live. It includes web app (Next.js), mobile app (React Native - Expo), and the browser extension (WXT - Vite + React). Each app has its own directory. + +### `packages` Directory + +This is where the shared code and the API for packages live. It includes the following: + +* shared libraries (database, mailers, cms, billing, etc.) +* shared features (auth, mails, billing, ai etc.) +* UI components (buttons, forms, modals, etc.) + +All apps can use and reuse the API exported from the packages directory. This makes it easy to have one, or many apps in the same codebase, sharing the same code. + +## Repository structure + +By default the monorepo contains the following apps and packages: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +## Web application structure + +The web application is located in the `apps/web` folder. It contains the following folders: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +--- +url: /docs/web/installation/update +title: Updating codebase +description: Learn how to update your codebase to the latest version. +--- + +If you've been following along with our previous guides, you should already have a Git repository set up for your project, with an `upstream` remote pointing to the original repository. + +Updating your project involves fetching the latest changes from the `upstream` remote and merging them into your project. Let's dive into the steps! + + + + ## Stash changes + + + If you don't have any changes to stash, you can skip this step and proceed with the update process. + + Alternatively, you can [commit](https://git-scm.com/docs/git-commit) your changes. + + + If you have any uncommitted changes, stash them before proceeding. It will allow you to avoid any conflicts that may arise during the update process. + + ```bash + git stash + ``` + + This command will save your changes in a temporary location, allowing you to retrieve them later. Once you're done updating, you can apply the stash to your working directory. + + ```bash + git stash apply + ``` + + + + ## Pull changes + + Pull the latest changes from the `upstream` remote. + + ```bash + git pull upstream main + ``` + + When prompted the first time, please opt for merging instead of rebasing. + + Don't forget to run `pnpm i` in case there are any updates in the dependencies. + + + + ## Resolve conflicts + + If there are any conflicts during the merge, Git will notify you. You can resolve them by opening the conflicting files in your code editor and making the necessary changes. + + + If you find conflicts in the `pnpm-lock.yaml file`, accept either of the two changes (avoid manual edits), then run: + + ```bash + pnpm i + ``` + + Your lock file will now reflect both your changes and the updates from the upstream repository. + + + + + ## Run a health check + + After resolving the conflicts, it's time to test your project to ensure everything is working as expected. Run your project locally and navigate through the various features to verify that everything is functioning correctly. + + For a quick health check, you can run: + + ```bash + pnpm lint + pnpm typecheck + + ``` + + If everything looks good, you're all set! Your project is now up to date with the latest changes from the `upstream` repository. + + + + ## Commit and push + + Once everything is working fine, don't forget to commit your changes using: + + ```bash + git commit -m "" + ``` + + and push them to your remote repository with: + + ```bash + git push origin + ``` + + + + +--- +url: /docs/web/internationalization/configuration +title: Configuration +description: Learn how to configure internationalization in TurboStarter. +--- + +The default global configuration is defined in the `@turbostarter/i18n` package and shared across all applications. You can override it in each app to customize the internationalization setup for that specific app. + +The configuration is defined in the `packages/i18n/src/config.ts` file: + +```ts title="packages/i18n/src/config.ts" +export const config = { + locales: ["en", "es"], + defaultLocale: "en", + namespaces: [ + "common", + "admin", + "organization", + "dashboard", + "auth", + "billing", + "marketing", + "validation", + ], + cookie: "locale", +} as const; +``` + +Let's break down the configuration options: + +* `locales`: An array of all supported locales. +* `defaultLocale`: The default locale to use if no other locale is detected. +* `namespaces`: An array of all namespaces used in the application. +* `cookie`: The name of the cookie to store the detected locale (acts as a cache). + +## Translation files + +The core of the whole internationalization setup is the translation files. They are stored in the `packages/i18n/src/translations` directory and are used to store the translations for each locale and namespace. + +Each directory represents a locale and contains a set of files, each corresponding to a specific namespace (e.g. `en/common.json`). Inside we define the keys and values for the translations. + +```ts title="packages/i18n/src/translations/en/common.json" +{ + "hello": "Hello, world!" +} +``` + +That way we can ensure that we have a single source of truth for the translations and we can use them consistently in all the applications. + +## Locales + +The `locales` array in the configuration defines the list of supported languages in your application. Each locale is represented by a string that uniquely identifies the language. + +To add a new locale, you need to: + +1. Add the new locale to the `locales` array in the configuration. +2. Create a new directory in the `packages/i18n/src/translations` directory. +3. Create a new file in the new directory for each namespace and add the translations for the new locale. + +For example, if you want to add the `fr` locale, you need to: + +1. Add `fr` to the `locales` array in the configuration. +2. Create a new directory in the `packages/i18n/src/translations` directory. +3. Create a new file for each namespace in the created directory and add the translations for the new locale. + +### Fallback locale + +The `defaultLocale` option in the configuration defines the fallback locale. If a translation is not found for a specific locale, the fallback locale will be used. + +We can also override this setting in each [app configuration](/docs/web/configuration/app) by configuring the `locale` property. + +## Namespaces + +`namespaces` are used to group translations by feature or module. This helps in organizing the translations and makes it easier to maintain them. + +### Why not one big namespace? + +Using multiple namespaces instead of one large namespace helps with: + +1. **Performance:** load translations on-demand instead of all at once, reducing the initial bundle size. +2. **Organization:** group translations by feature (e.g., `auth`, `common`, `dashboard`). +3. **Maintenance:** easier to update and manage smaller translation files. +4. **Development:** better TypeScript support and team collaboration. + +For example, you might structure your namespaces like this: + + + + ```ts title="packages/i18n/src/translations/en/common.json" + { + "hello": "Hello, world!" + } + ``` + + + + ```ts title="packages/i18n/src/translations/en/auth.json" + { + "login": "Login", + "register": "Register" + } + ``` + + + + ```ts title="packages/i18n/src/translations/en/billing.json" + { + "invoice": "Invoice", + "payment": "Payment", + "subscription": "Subscription" + } + ``` + + + +Remember that while you can create as many namespaces as needed, it's important to maintain a balance - too many namespaces can lead to unnecessary complexity, while too few might defeat the purpose of separation. + +## Routing + +TurboStarter implements locale-based routing by placing pages under the `[locale]` folder. However, the default locale (usually `en`) is not prefixed in the URL for better SEO and user experience. + +For example, with English as the default locale and Polish as an additional language: + +* `/dashboard` → English version (default locale) +* `/pl/dashboard` → Polish version + +The app also automatically detects the user's preferred language through cookies, HTML `lang` attribute, and browser's `Accept-Language` header. + +This ensures a seamless experience where users get content in their preferred language while maintaining clean URLs for the default locale. + + + You can override the locale by manually setting the cookie or by navigating to + a URL with a different locale prefix. + + + +--- +url: /docs/web/internationalization/overview +title: Overview +description: Get started with internationalization in TurboStarter. +--- + +TurboStarter uses [i18next](https://www.i18next.com/) for internationalization, which is one of the most popular and mature (over 10 years of development!) i18n frameworks for JavaScript. + + + With i18next, you can easily translate your application into multiple + languages, handle complex pluralization rules, format dates and numbers + according to locale, and much more. The framework is highly extensible through + plugins and provides excellent TypeScript support out of the box. + + +You can read more about `i18next` package in the [official documentation](https://www.i18next.com/overview/getting-started). + +![i18next logo](/images/docs/i18next.jpg) + +## Getting started + +TurboStarter comes with `i18next` pre-configured and abstracted behind the `@turbostarter/i18n` package. This abstraction layer ensures that any future changes to the underlying translation library won't impact your application code. The internationalization setup is ready to use out of the box and includes: + +* Multiple language support out of the box +* Type-safe translations with generated types +* Automatic language detection +* Easy-to-use React hooks for translations +* Built-in number and date formatting +* Support for nested translation keys +* Pluralization handling + +To start using internationalization in your app, you'll need to: + +1. Configure your supported languages +2. Add translation files +3. Use translation hooks in your components + +Check out the following sections to learn more about each step: + + +--- +url: /docs/web/internationalization/translations +title: Translating app +description: Learn how to translate your application to multiple languages. +--- + +TurboStarter provides a flexible and powerful translation system that works seamlessly across your entire application. Whether you're working with React Server Components (RSC), client-side components, or server-side rendering, you can easily integrate translations to create a fully internationalized experience. + +The translation system supports: + +* **Server components (RSC)** for efficient server-side translations +* **Client components** for dynamic language switching +* **Server-side rendering** for SEO-friendly translated content + +## Server components (RSC) + +To get the translations in a server component, you can use the `getTranslation` method: + +```tsx +import { getTranslation } from "@turbostarter/i18n"; + +export default async function MyComponent() { + const { t } = await getTranslation(); + + return
{t("common:hello")}
; +} +``` + +There is also a possibility to use the [Trans](https://react.i18next.com/latest/trans-component) component, which could be useful e.g. for interpolating variables: + +```tsx +import { Trans } from "@turbostarter/i18n"; +import { withI18n } from "@turbostarter/i18n/with-i18n"; + +const Page = () => { + return }} />; +}; + +export default withI18n(Page); +``` + +Although, to make it available in the server component, you need to wrap it with the `withI18n` HOC. + +Given that server components are rendered in parallel, it's uncertain which one will render first. Therefore, it's crucial to initialize the translations before rendering the server component on each page/layout. + +## Client components + +For client components, you can use the `useTranslation` hook from the `@turbostarter/i18n` package: + +```tsx +"use client"; + +import { useTranslation } from "@turbostarter/i18n"; + +export default function MyComponent() { + const { t } = useTranslation(); + + return
{t("common:hello")}
; +} +``` + +That's the simplest way to get the translations in a client component. + +## Server-side + +In all other places (e.g. metadata, API routes, sitemaps etc.) you can use the `getTranslation` method to get the translations server-side: + +```ts +import { getTranslation } from "@turbostarter/i18n"; + +export const generateMetadata = async () => { + const { t } = await getTranslation(); + + return { + title: t("common:title"), + }; +}; +``` + +It automatically checks the user's preferred locale and uses the correct translation. + +## Language switcher + +TurboStarter ships with a language customizer component that allows you to switch between languages. You can import and use the `LocaleCustomizer` component and drop it anywhere in your application to allow users to change the language seamlessly. + +```tsx +import { LocaleCustomizer } from "@turbostarter/ui-web/i18n"; + +export default function MyComponent() { + return ; +} +``` + +The component automatically displays all languages configured in your i18n settings. When a user switches languages, it will: + +1. Update the URL to include the new locale prefix (e.g. `/es/dashboard`) +2. Store the selected locale in a cookie for persistence +3. Refresh translations across the entire application +4. Preserve the current page/route during the language switch + +This provides a seamless localization experience without requiring any additional configuration. + +## Best practices + +Here are some recommended best practices for managing translations in your application: + +* Use descriptive translation keys that follow a logical hierarchy + + ```ts + // ✅ Good + "auth.login.title"; + + // ❌ Bad + "loginTitleForAuth"; + ``` + +* Keep translations organized in separate namespaces/files based on features or sections + + ``` + translations/ + ├── en/ + │ ├── auth.json + │ └── common.json + └── pl/ + ├── auth.json + └── billing.json + ``` + +* Avoid hardcoding text strings - always use translation keys even for seemingly static content + +* Always provide a fallback language (usually English) for when translations are missing + +* Use pluralization and interpolation features when dealing with dynamic content + + ```ts + // Pluralization + t("items", { count: 2 }); // "2 items" + + // Interpolation + t("welcome", { name: "John" }); // "Welcome, John!" + ``` + +* Regularly review and clean up unused translation keys to keep files maintainable + +* Use TypeScript for type-safe translation keys + + +--- +url: /docs/web/marketing/legal +title: Legal pages +description: Learn how to create and update legal pages +--- + +Legal pages are defined in the `apps/web/src/app/[locale]/(marketing)/legal` directory. + +TurboStarter comes with the following legal pages: + +* **Terms and Conditions**: to define the terms and conditions of your application +* **Privacy Policy**: to define the privacy policy of your application +* **Cookie Policy**: to define the cookie policy of your application + +For obvious reasons, **these pages are empty and you need to fill in the content.** + +## Content from CMS + +Content for legal pages are stored as [MDX](https://mdxjs.com/) files in [content collection](/docs/web/cms/content-collections) in `packages/cms/src/content/collections/legal` directory. + +Then it's parsed and rendered as a Next.js page under corresponding slug: + +```tsx title="apps/web/src/app/[locale]/(marketing)/legal/[slug]/page.tsx" +import { + CollectionType, + getContentItemBySlug, + getContentItems, +} from "@turbostarter/cms"; + +export default async function Page({ params }: PageParams) { + const item = getContentItemBySlug({ + collection: CollectionType.LEGAL, + slug: (await params).slug, + locale: (await params).locale, + }); + + if (!item) { + return notFound(); + } + + return ; +} + +export function generateStaticParams() { + return getContentItems({ collection: CollectionType.LEGAL }).items.map( + ({ slug, locale }) => ({ + slug, + locale, + }), + ); +} +``` + +As it's fully typesafe it also allows us to generate metadata for each page based on the frontmatter that you define in the MDX file: + +```tsx title="apps/web/src/app/[locale]/(marketing)/legal/[slug]/page.tsx" +export async function generateMetadata({ params }: PageParams) { + const item = getContentItemBySlug({ + collection: CollectionType.LEGAL, + slug: (await params).slug, + locale: (await params).locale, + }); + + if (!item) { + return notFound(); + } + + return getMetadata({ + title: item.title, + description: item.description, + })({ params }); +} +``` + +Read more about it in the [CMS section](/docs/web/cms/overview). + +## ChatGPT prompts + +Each `.mdx` file with legal content include a set of useful prompts that you can use to generate the content. + + + Please, be aware that **ChatGPT is not a lawyer** and the content generated by it should be reviewed by one before publishing. Take your time and treat the generated content as a starting point not a final document. + + +```mdx title="privacy-policy.mdx" +--- +title: Privacy Policy +description: Our privacy policy outlines how we collect, use, and protect your personal information. +--- + +{/* 💡 You can use one of the following ChatGPT prompts to generate this 💡 */} + +... +``` + +Feel free to add your own content or even additional pages to the `legal` collection. + + +--- +url: /docs/web/marketing/pages +title: Marketing pages +description: Discover which marketing pages are available out of the box and how to add a new one. +--- + +TurboStarter comes with pre-defined marketing pages to help you get started with your SaaS application. These pages are built with Next.js and Tailwind CSS and are located in the `apps/web/src/app/[locale]/(marketing)` directory. + +TurboStarter comes with the following marketing pages: + +* **Home**: conversions-optimized [landing page](https://demo.turbostarter.dev) with [hero section](https://demo.turbostarter.dev#hero), [features](https://demo.turbostarter.dev#features), [pricing](https://demo.turbostarter.dev#pricing), [testimonials](https://demo.turbostarter.dev#testimonials), [FAQ](https://demo.turbostarter.dev#faq) and more +* [Blog](/docs/web/cms/blog): to display your blog posts +* **Pricing**: to display your pricing plans +* **Contact**: to enable users to contact you with a contact form + +## Contact form + +To make the contact form work, you need to add the following environment variable: + +```dotenv +CONTACT_EMAIL= +``` + +Set this variable to the email address where you want to receive contact form submissions. The sender's email address will match what you configured in your [mailing configuration](/docs/web/emails/configuration). + +## Adding a new marketing page + +To add a new marketing page, create a new directory in `apps/web/src/app/[locale]/(marketing)` with the desired route name. + +The page will automatically become available in your application at the corresponding URL path. + +For example, to create a page accessible at `/about`, create a directory named `about` and add a `page.tsx` file inside it. The complete path would be `apps/web/src/app/[locale]/(marketing)/about/page.tsx`. + +```tsx title="apps/web/src/app/[locale]/(marketing)/about/page.tsx" +export default function AboutPage() { + return
About
; +} +``` + +This page inherits the layout at `apps/web/src/app/[locale]/(marketing)/layout.tsx`. You can customize the layout by editing this file - but remember that it will affect all marketing pages. + + +--- +url: /docs/web/marketing/seo +title: SEO +description: Learn how to optimize your app for search engines. +--- + +SEO is an important part of building a website. It helps search engines understand your website and rank it higher in search results. In this guide, you'll learn how to improve your SaaS application's search engine optimization (SEO). + + + TurboStarter is already optimized for SEO out of the box (including meta tags, sitemaps, robots files and many more). However, there are a few things you can do to improve your application's SEO. + + +**Content:** High-quality, relevant content is the cornerstone of effective SEO. Focus on **creating valuable, engaging content** that addresses your customers' needs and questions. Regularly update your content to keep it fresh and relevant. + +**Keyword optimization:** Conduct thorough keyword research to identify terms your target audience is searching for. Incorporate these keywords naturally into your content, titles, meta descriptions, and headers. Avoid keyword stuffing; prioritize readability and user experience. + +**On-Page SEO:** + +* Use descriptive, keyword-rich titles and meta descriptions for each page. +* Implement a clear heading structure (H1, H2, H3) to organize your content. +* Optimize images with descriptive file names and alt text. +* Ensure your URLs are clean, descriptive, and include relevant keywords. + +**Technical SEO:** + +* Improve website loading speed by optimizing images, minifying CSS and JavaScript, and leveraging browser caching. +* Ensure your website is mobile-friendly and responsive across all devices. +* Implement schema markup to help search engines better understand your content. +* Use HTTPS to secure your website and boost search rankings. + +**User experience:** + +* Design an intuitive site structure and navigation to improve user engagement. +* Reduce bounce rates by creating compelling, easy-to-read content. +* Implement internal linking to guide users through your site and distribute page authority. + +**Link building:** + +* Create high-quality, shareable content to naturally attract backlinks. +* Engage in guest posting on reputable sites within your industry. +* Participate in industry forums and discussions, providing valuable insights and linking to your content when relevant. +* Leverage social media to increase content visibility and encourage sharing. + +**Local SEO (if applicable):** + +* Claim and optimize your Google My Business listing. +* Ensure consistent NAP (Name, Address, Phone) information across all online directories. +* Encourage customer reviews on Google and other relevant platforms. + +**Monitor and analyze:** + +* Use [Google Search Console](https://search.google.com/search-console/about) to monitor your site's performance in search results and identify issues. +* Regularly analyze your SEO efforts using tools like Google Analytics to understand user behavior and refine your strategy. + +**Stay updated:** + +* Keep abreast of SEO best practices and algorithm updates to continually refine your strategy. +* Regularly audit your website to identify and fix any SEO issues. + +## Sitemap + +Generally speaking, Google will find your pages without a sitemap as it follows the link in your website. However, you can add pages to the sitemap by adding them to the `apps/web/src/app/sitemap.ts` file, which is used to generate the sitemap. + +If you add more static pages to your website, you can add them to the sitemap by adding them to the `apps/web/src/app/sitemap.ts` returned array. + +```tsx title="sitemap.ts" +export default function sitemap(): MetadataRoute.Sitemap { + return [ + { + ...getEntry(pathsConfig.index), + lastModified: new Date(), + changeFrequency: "monthly", + priority: 1, + }, + ...getContentItems({ + collection: CollectionType.BLOG, + locale: appConfig.locale, + }).items.map((post) => ({ + ...getEntry(pathsConfig.marketing.blog.post(post.slug)), + lastModified: new Date(post.lastModifiedAt), + changeFrequency: "monthly", + priority: 0.7, + })), + + /* other pages */ + ]; +} +``` + +All the existing pages are already added to the sitemap. You don't need to add them manually. + +## Meta tags + +TurboStarter provides a helper function called `getMetadata` to easily set meta tags for your pages. This helper ensures consistent metadata formatting across your site and includes essential SEO tags like title, description, and Open Graph tags. You can use it in any page's metadata export: + +```tsx title="page.tsx" +export const generateMetadata = getMetadata({ + title: "My Page Title", + description: "My Page Description", +}); +``` + +This will generate the following meta tags: + +```html + + + +``` + +The `getMetadata` helper is really useful for generating consistent meta tags across your site, making SEO optimization simpler and more reliable. + + + `getMetadata` also supports translations. You can pass a translation key to the `title` and `description` parameters, and it will automatically use the correct translation for the current locale. + + ```tsx + export const generateMetadata = getMetadata({ + title: "billing:title", + description: "billing:description", + }); + ``` + + In this example, the `title` and `description` will be fetched from the `billing` namespace for the current locale and placed in the meta tags. + + +## Backlinks + +Backlinks are said to be the **most important factor** in modern SEO. The more backlinks you have from high-quality websites, the higher your website will rank in search results - and the more traffic you'll get. + +How do you acquire backlinks? The most effective strategy is to create high-quality, valuable content that naturally attracts links from other websites. However, there are several other methods to build backlinks: + +1. **Guest blogging:** Contribute articles to reputable websites within your industry. This not only provides backlinks but also exposes your brand to a new audience. +2. **Strategic outreach:** Identify websites that could benefit from linking to your content. Reach out with a personalized pitch, explaining the value your content adds to their audience. +3. **Digital PR:** Create newsworthy content or conduct original research that journalists and bloggers will want to reference and link to. +4. **Broken link building:** Find broken links on relevant websites and suggest your content as a replacement. +5. **Resource page link building:** Find resource pages in your niche and suggest your content for inclusion. +6. **Social media engagement:** While not directly impacting SEO, active social media presence can increase content visibility and indirectly lead to more backlinks. +7. **Create linkable assets:** Develop infographics, tools, or comprehensive guides that others in your industry will want to reference. +8. **Participate in industry forums and discussions:** Contribute meaningfully to conversations in your field, including your website when relevant. + +Remember, the quality of backlinks is more important than quantity. Focus on acquiring links from authoritative, relevant websites in your niche. Avoid any black-hat techniques or link schemes that could result in penalties from search engines. + +## Adding your website to Google Search Console + +Once you've optimized your website for SEO, you can add it to Google Search Console. Google Search Console is a free tool that helps you monitor and maintain your website's presence in Google search results. + +You can use it to check your website's indexing status, submit sitemaps, and get insights into how Google sees your website. + +The first thing you need to do is verify your website in Google Search Console. You can do this by adding a meta tag to your website's HTML or by uploading an HTML file to your website. + +Once you've verified your website, you can submit your sitemap to Google Search Console. This will help Google find and index your website's pages faster. + +Please submit your sitemap to Google Search Console by going to the `Sitemaps` section and adding the URL of your sitemap. The URL of your sitemap is `https://your-website.com/sitemap.xml`. + +Of course, please replace `your-website.com` with your actual website URL. + +## Content + +When it comes to internal factors, **content is king**. Make sure your content is relevant, useful, and engaging. Make sure it's updated regularly and optimized for SEO. + + + Most importantly, you want to think about how your customers will search for the problem your SaaS is solving. For example, if you're building a project management tool, you might want to write about project management best practices, how to manage a remote team, or how to use your tool to improve productivity. + + +You can use the blog and documentation features in TurboStarter to create high-quality content that will help your website rank higher in search results - and help your customers find what they're looking for. + +## Indexing and ranking take time + +New websites can take a while to get indexed by search engines. It can take anywhere from a few days to a few weeks (in some cases, even months!) for your website to show up in search results. Be patient and keep updating your content and optimizing your website for search engines. + +Also, you can edit `robots.ts` file to control which pages are indexed by search engines: + +```tsx title="robots.ts" +export default function robots(): MetadataRoute.Robots { + return { + rules: { + userAgent: "*", + allow: "/", + disallow: ["/api", "/dashboard", "/auth"], + }, + sitemap: appConfig.url + "/sitemap.xml", + }; +} +``` + +Remember, **SEO is an ongoing process.** Consistently apply these practices and adapt your strategy based on performance data and industry changes to improve your search engine visibility over time. + + +--- +url: /docs/web/monitoring/overview +title: Overview +description: Get started with web monitoring in TurboStarter. +--- + +TurboStarter includes lightweight monitoring hooks so you can quickly answer: **what's failing**, **where it's failing**, and **who it's affecting**. Out of the box, the web app can report exceptions from both the client and the server, and it's designed to be easy to extend with your preferred provider. + +## Capturing exceptions + +Monitoring starts with capturing exceptions reliably in the places that matter most: + +* **Client-side errors**: the Next.js App Router error boundary reports unexpected runtime errors so you get visibility without leaving users stuck on a broken screen. +* **Server-side errors**: API failures (for example, Hono errors in production) can be reported with a stable, anonymous distinct id so you can spot recurring issues and correlate them with sessions. +* **Manual reporting**: you can also report exceptions from your own `try/catch` blocks to add extra context around critical flows (payments, onboarding, imports, etc.). + + + + ```tsx + "use client"; + + import { captureException } from "@turbostarter/monitoring-web"; + + export default function ExampleComponent() { + const handleClick = () => { + try { + /* some risky operation */ + } catch (error) { + captureException(error); + } + }; + + return ; + } + ``` + + + + ```ts + import { captureException } from "@turbostarter/monitoring-web/server"; + + try { + /* do something */ + } catch (error) { + captureException(error); + } + ``` + + + + + Make sure to use the correct import for the `captureException` function. We're using the same name for both client and server monitoring, but they are different functions. For server-side, just add `/server` to the import path (`@turbostarter/monitoring-web/server`). + + + + ```tsx + import { captureException } from "@turbostarter/monitoring-web"; + ``` + + + + ```tsx + // [!code word:server] + import { captureException } from "@turbostarter/monitoring-web/server"; + ``` + + + + +## Identifying users + +Exception reports become dramatically more actionable once they're tied to a real user. TurboStarter automatically identifies signed-in users (based on the current auth session), which allows your monitoring provider to associate exceptions and sessions with a user profile. + +If you want richer debugging, identify users with traits (like email, plan, or role) so you can filter and segment issues by the people impacted. + +```tsx title="monitoring.tsx" +"use client"; + +import { useEffect } from "react"; +import { identify } from "@turbostarter/monitoring-web"; +import { authClient } from "~/lib/auth/client"; + +export const MonitoringProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const session = authClient.useSession(); + + useEffect(() => { + if (session.isPending) { + return; + } + + identify(session.data?.user ?? null); + }, [session]); + + return <>{children}; +}; +``` + + + On the server, there are no dedicated identification helper. Most providers that support user-level tracking expect you to pass an identifier or traits directly within the `captureException` call (for example, as a `userId` or similar property), so make sure to check your specific provider's documentation for the recommended way to include user information. + + +## Providers + +The starter implements multiple providers for managing monitoring. To learn more about each provider and how to configure them, see their respective sections: + + + + + + + +Configuration and setup are handled for you via a unified API, making it easy to switch monitoring providers by just updating the exports. You can also add custom providers without disrupting any monitoring-related logic. + +## Best practices + +Below are some guidelines to keep monitoring useful, low-noise, and privacy-safe. + + + + Report unexpected exceptions and failed business-critical operations; avoid + logging “expected” states (validation errors, user cancellations, missing + optional data). + + + + Include what the user was doing (route/action), relevant IDs (request id, + order id), and a clear message so you can reproduce and triage quickly. + + + + Identify with stable IDs; only attach traits that are necessary for + debugging. Don’t send secrets or sensitive fields (tokens, passwords, raw + payment details). + + + + If a loop or retry can fire many times, guard your capture calls so you + don’t spam your provider (and your budget). + + + + Keep dev/staging/prod isolated (separate projects or environment tags) so + production alerts stay meaningful. + + + + Set alerts for spikes in error rate, degraded performance, and failures in + critical flows (auth, checkout, billing webhooks), not for every single + exception. + + + +Application monitoring helps you track errors, exceptions, and performance issues for better app reliability. With multiple provider support, you can quickly spot and resolve problems. + +Focus on actionable errors, useful context, and user privacy to get the most value from your monitoring. + + +--- +url: /docs/web/monitoring/posthog +title: PostHog +description: Learn how to setup PostHog as your web monitoring provider. +--- + +[PostHog](https://posthog.com/) is a comprehensive product analytics platform that includes error tracking, session replay, feature flags, and more. It helps developers identify, diagnose, and fix issues in their applications by capturing and reporting errors and exceptions in real time. + +With features like automatic error reporting, stack trace visualization, and user/session context, PostHog provides deep insight into how your application is behaving in production so you can quickly resolve problems and improve reliability. + + + To use PostHog as your monitoring provider, you need to have an account. You can create one [here](https://app.posthog.com/signup) or [self-host](https://posthog.com/docs/self-host) it. + + + + PostHog is also one of pre-configured providers for [analytics](/docs/web/analytics/overview) in TurboStarter. You can learn more about it [here](/docs/web/analytics/configuration#posthog). + + +![PostHog banner](/images/docs/web/monitoring/posthog/banner.jpg) + +## Configuration + +PostHog integrates seamlessly with TurboStarter, enabling you to monitor application errors and performance from development to production. By configuring PostHog as your monitoring provider, you'll be able to detect, track, and resolve issues proactively, leading to a more stable and reliable app. + +Follow the simple setup instructions below to get started with PostHog in your TurboStarter project. + + + + ### Create a project + + First, you need to create a [project](https://app.posthog.com/project/settings) in PostHog. You can do it directly from your [dashboard](https://app.posthog.com) by clicking on the *New Project* button. + + + + ### Activate PostHog as your monitoring provider + + The monitoring provider to use is determined by the exports in `packages/monitoring/web` package. To activate PostHog as your monitoring provider, you need to update the exports in: + + + + ```ts + // [!code word:posthog] + export * from "./posthog"; + ``` + + + + ```ts + // [!code word:posthog] + export * from "./posthog/server"; + ``` + + + + ```ts + // [!code word:posthog] + export * from "./posthog/env"; + ``` + + + + If you want to customize the provider, you can find its definition in `packages/monitoring/web/src/providers/posthog` directory. + + + + ### Set environment variables + + Based on your [project settings](https://app.posthog.com/project/settings), fill the following environment variables in your `.env.local` file in `apps/web` directory and your deployment environment: + + ```dotenv title="apps/web/.env.local" + NEXT_PUBLIC_POSTHOG_KEY="your-posthog-api-key" + NEXT_PUBLIC_POSTHOG_HOST="https://us.i.posthog.com" + ``` + + + +That's it! You can now start your app and see the errors and exceptions in your [PostHog dashboard](https://app.posthog.com/project/error_tracking). + +![PostHog error](/images/docs/web/monitoring/posthog/error.png) + +Feel free to customize the configuration to your needs. For more information, please refer to the [PostHog documentation](https://posthog.com/docs/error-tracking/installation/nextjs). + + + + + + + +## Uploading source maps + +**Source maps** are files that map your minified or transpiled code (such as the JavaScript code generated by frameworks like Next.js) back to your original source code (for example, TypeScript or unbundled JavaScript). When your app is running in production, the code is often bundled and minified to improve performance, which makes stack traces and error messages hard to read and debug. + + + With source maps enabled and uploaded to your monitoring provider (like PostHog), error reports include references to the original lines of your source code, not just the processed/minified output. + + +PostHog can automatically provide readable stack traces for errors using source maps. The `@posthog/nextjs-config` package handles source map generation and upload automatically during the build process. + +To start using source maps, install the package `@posthog/nextjs-config` in `apps/web/package.json` as a dependency. + +```bash +pnpm i @posthog/nextjs-config --filter web +``` + +Next, extend your app's Next.js options by adding `withPostHogConfig` into the `next.config.ts` file: + +```ts title="apps/web/next.config.ts" +import { withPostHogConfig } from "@posthog/nextjs-config"; + +const config = { + /* existing Next.js configuration options */ +}; + +export default withPostHogConfig(config, { + personalApiKey: process.env.POSTHOG_API_KEY, + envId: process.env.POSTHOG_ENV_ID, + host: process.env.NEXT_PUBLIC_POSTHOG_HOST, + sourcemaps: { + enabled: true, // Enable sourcemaps generation and upload + project: "my-application", // Optional: Project name, defaults to repository name + version: "1.0.0", // Optional: Release version, defaults to current git commit + deleteAfterUpload: true, // Delete sourcemaps after upload, defaults to true + }, +}); +``` + +Make sure you have set the following environment variables locally and in your deployment environment: + +* `POSTHOG_ERROR_TRACKING_API_KEY` - Your [Personal API Key](https://app.posthog.com/settings/user-api-keys#variables) with write access on error tracking +* `POSTHOG_PROJECT_ID` - Project ID from [project settings](https://app.posthog.com/settings/environment#variables) +* `NEXT_PUBLIC_POSTHOG_HOST` - Your PostHog instance URL + + + Before proceeding, confirm that source maps are being generated by checking for `.js.map` files in your `dist` directory. These are the symbol sets that will be used to unminify stack traces in PostHog. + + Next, confirm that source maps are successfully uploaded to PostHog by checking the [symbol sets](https://app.posthog.com/project/settings/symbol-sets) section in your project settings. + + Finally, confirm that the served files are injected with the correct source map comment in production. You can do this by inspecting your deployed app in browser dev tools and looking for a comment like this at the end of your JavaScript bundles: + + ```js + //# chunkId=0197e6db-9a73-7b91-9e80-4e1b7158db5c + ``` + + +Once everything is set up, PostHog will provide you with detailed, easy-to-read error reports that link directly back to your original source code - even after your code has been bundled or minified. This makes diagnosing and fixing production issues much simpler. + + + + + + + + +--- +url: /docs/web/monitoring/sentry +title: Sentry +description: Learn how to setup Sentry as your web monitoring provider. +--- + +[Sentry](https://sentry.io/) is a popular error monitoring and performance tracking platform. It helps developers identify, diagnose, and fix issues in their applications by capturing and reporting errors and exceptions in real time. + +With features like automatic error reporting, stack trace visualization, and user/session context, Sentry provides deep insight into how your application is behaving in production so you can quickly resolve problems and improve reliability. + + + To use Sentry as your monitoring provider, you need to have an account. You can create one [here](https://sentry.io/signup). + + +![Sentry banner](/images/docs/web/monitoring/sentry/banner.png) + +## Configuration + +Sentry integrates seamlessly with TurboStarter, enabling you to monitor application errors and performance from development to production. By configuring Sentry as your monitoring provider, you’ll be able to detect, track, and resolve issues proactively, leading to a more stable and reliable app. + +Follow the simple setup instructions below to get started with Sentry in your TurboStarter project. + + + + ### Create a project + + First, you need to create a [project](https://docs.sentry.io/product/projects/) in Sentry. You can do it directly from your [dashboard](https://sentry.io/settings/account/projects/) by clicking on the *Create Project* button. + + + + ### Activate Sentry as your monitoring provider + + The monitoring provider to use is determined by the exports in `packages/monitoring/web` package. To activate Sentry as your monitoring provider, you need to update the exports in: + + + + ```ts + // [!code word:sentry] + export * from "./sentry"; + ``` + + + + ```ts + // [!code word:sentry] + export * from "./sentry/server"; + ``` + + + + ```ts + // [!code word:sentry] + export * from "./sentry/env"; + ``` + + + + If you want to customize the provider, you can find its definition in `packages/monitoring/web/src/providers/sentry` directory. + + + + ### Set environment variables + + Based on your [project settings](https://sentry.io/project/settings), fill the following environment variables in your `.env.local` file in `apps/web` directory and your deployment environment: + + ```dotenv title="apps/web/.env.local" + NEXT_PUBLIC_SENTRY_DSN="your-sentry-dsn" + NEXT_PUBLIC_PROJECT_ENVIRONMENT="your-project-environment" + ``` + + + + ### Apply instrumentation to your app + + Install the package `@sentry/nextjs` in `apps/web/package.json` as a dependency. + + ```bash + pnpm i @sentry/nextjs --filter web + ``` + + Next, extend your app's Next.js options by adding `withSentryConfig` into the `next.config.ts` file: + + ```ts title="apps/web/next.config.ts" + import { withSentryConfig } from "@sentry/nextjs"; + + const config = { + /* existing Next.js configuration options */ + }; + + export default withSentryConfig(config, { + org: "your-sentry-org", + project: "your-sentry-project", + }); + ``` + + + +That's it! You can now start your app and see the errors and exceptions in your [Sentry dashboard](https://sentry.io/settings/account/projects/). + +![Sentry error](/images/docs/web/monitoring/sentry/error.jpg) + +Feel free to customize the configuration to your needs. For more information, please refer to the [Sentry documentation](https://docs.sentry.io/platforms/javascript/guides/nextjs/). + + + + + + + +## Uploading source maps + +**Source maps** are files that map your minified or transpiled code (such as the JavaScript code generated by frameworks like Next.js) back to your original source code (for example, TypeScript or unbundled JavaScript). When your app is running in production, the code is often bundled and minified to improve performance, which makes stack traces and error messages hard to read and debug. + + + With source maps enabled and uploaded to your monitoring provider (like Sentry), error reports include references to the original lines of your source code, not just the processed/minified output. + + +Sentry can automatically provide readable stack traces for errors using source maps, requiring a [Sentry auth token](https://docs.sentry.io/account/auth-tokens/). + +Update your `next.config.ts` file with the following options: + +```ts title="apps/web/next.config.ts" +import { withSentryConfig } from "@sentry/nextjs"; + +const config = { + /* existing Next.js configuration options */ +}; + +export default withSentryConfig(config, { + org: "your-sentry-org", + project: "your-sentry-project", + + // An auth token is required for uploading source maps. + authToken: process.env.SENTRY_AUTH_TOKEN, // [!code ++] + + // Upload a larger set of source maps for prettier stack traces (increases build time) + widenClientFileUpload: true, // [!code ++] +}); +``` + +Then, set the `SENTRY_AUTH_TOKEN` environment variable in your `.env.local` file in `apps/web` directory and your deployment environment: + +```dotenv title="apps/web/.env.local" +SENTRY_AUTH_TOKEN="your-sentry-auth-token" +``` + +With these steps, your Sentry integration will give you clear, actionable error reports tied directly to your source code - even after bundling and minification. This makes it much easier to debug and resolve production issues. + +Take a moment to test your setup and ensure source maps are correctly resolving stack traces in your [Sentry dashboard](https://sentry.io/settings/account/projects/). For deeper customization or additional troubleshooting, always consult the [official Sentry documentation](https://docs.sentry.io/platforms/javascript/guides/nextjs/sourcemaps/). + + + + + + + + +--- +url: /docs/web/organizations/active-organization +title: Active organization +description: Set and switch the current organization context within your application. +--- + +The active organization is tracked based on the **URL slug** and the **session state**. We made it **as simple as possible** to use, introducing our custom hooks and an abstraction to sync it both ways. + +Below you can find more details about how to access the active organization across different contexts in your application. + +You can customize the behavior to your needs—for example, to restrict users to at most one organization at a time. + +## Server component + +You have two separate ways to access the active organization of the currently logged-in user on the server: + +* from the URL slug (organization-scoped routes) +* from the session (when no slug is present or you don't want to use it) + +We recommend always using the URL slug when you're doing something inside an organization-scoped route. This keeps the URL as the source of truth and works seamlessly with SSR and caching. + +```tsx title="page.tsx" +import { getOrganization } from "~/lib/auth/server"; + +export default async function Page({ + params, +}: { + params: Promise<{ + organization: string; + }>; +}) { + const organization = (await params).organization; + const activeOrganization = await getOrganization({ slug: organization }); + + return <>{activeOrganization?.name}; +} +``` + +Alternatively, you can use the session to access the active organization. This reads `session.activeOrganizationId` and resolves the organization by its stable ID. + +```tsx title="page.tsx" +import { getOrganization, getSession } from "~/lib/auth/server"; + +export default async function Page() { + const { session } = await getSession(); + const activeOrganization = await getOrganization({ + id: session.activeOrganizationId, + }); + + return <>{activeOrganization?.name}; +} +``` + +Be aware that sometimes you might encounter synchronization issues between the URL slug and the session, for example when a user opens multiple tabs to different organizations. More on this in the [Edge cases](#edge-cases) section. + +## Client component + +On the client side, we designed a dedicated hook to access the active organization - `useActiveOrganization`. It's a simple wrapper around the API that returns the active organization based on the URL slug or the session. It also helps keep the state in sync with the server session. + +```tsx title="client.tsx" +"use client"; + +import { useActiveOrganization } from "~/lib/hooks/use-active-organization"; + +export default function ClientComponent() { + const { activeOrganization, activeMember } = useActiveOrganization(); + + return ( + <> +

{activeOrganization?.name}

+

{activeMember?.role}

+ + ); +} +``` + +Using the hook is recommended over direct API calls, as it will keep the state in sync with the server session. + +It also returns the active member of the active organization, so you can access the user's role and other member-specific data. + +## API route + +To access the active organization data in an API route, you can read it from the session that is appended to the context when you use [authentication middleware](/docs/web/api/protected-routes). + +```ts title="action/router.ts" +export const actionRouter = new Hono().post("/", enforceAuth, async (c) => { + const organizationId = c.var.user.activeOrganizationId; + const organization = await getOrganization({ id: organizationId }); + return c.json(organization); +}); +``` + +Although it's the simplest way, we recommend directly passing the `organizationId` together with the payload when you need to perform an action. + +```ts title="action/router.ts" +export const actionRouter = new Hono().post( + "/", + enforceAuth, + validate( + "json", + z.object({ + organizationId: z.string(), + /* rest of the payload */ + }), + ), + async (c) => { + const { organizationId, ...payload } = c.req.valid("json"); + const organization = await getOrganization({ id: organizationId }); + return c.json(await performAction(organization, payload)); + }, +); +``` + +This ensures that the action is performed on the correct organization, even if the user has multiple organizations open in different tabs. See [Edge cases](#edge-cases) for more details. + +## Edge cases + +* **Expected and harmless:** Short periods where the URL slug and server session differ can happen (for example, with multiple tabs or quick switching). The active tab always treats the slug as the source of truth and the session catches up. +* **Multiple tabs:** Each tab maintains its own org context from its slug. As you switch focus, the shared session updates; brief divergence is normal and safe. +* **Rapid switching/slow network:** During fast navigation or poor connectivity, you may momentarily see the previous org while the session updates. Show a small loading state; cancel in-flight requests tied to the old org. +* **Missing/invalid slug:** If the slug is missing or invalid, we fall back to the session’s `activeOrganizationId` or redirect to a safe default. +* **Access or permission changes:** If a user loses access to the org they’re viewing, the data is cleared from the session and the user is redirected to a valid organization or personal dashboard. + + + Whenever the active organization changes, the server session is updated and the client is redirected to the new organization scope. + + All caches keyed by organization are invalidated to avoid leaking data between organizations. + + + +--- +url: /docs/web/organizations/data-model +title: Data model +description: Entities and relationships for organizations and multi-tenancy. +--- + +Our multi-tenant model is organized around the concept of an **organization**. An organization represents a single tenant and is the primary boundary for data isolation, access control, and routing. + +Users can belong to multiple organizations through a membership. Invitations let organization admins onboard new members by email with a specific role. + + + +## Entities + +### Organization + +The tenant. Stores human-friendly `name`, unique `slug` (used in URLs and lookups), optional `logo`, and optional `metadata` for extensibility (feature flags, billing context, UI preferences, etc.). `createdAt` provides auditability. The `slug` is globally unique to keep URLs stable and predictable. + +### User + +The identity of a person. Users are global and can join many organizations. Account-level fields (e.g., `name`, `email`, verification, avatar, security flags) live here. + + + A user's application-wide properties (like a global `role` or moderation flags) are distinct from their per-organization role. + + +### Member (Membership) + +The join between a `user` and an `organization`. This is where multi-tenancy permissions are enforced. Each membership stores the `role` the user holds in that specific organization (for example, `member`, `admin`). + +Memberships include timestamps for auditing and can be cascaded when a user or organization is removed. + +### Invitation + +Represents an invite to join an organization by `email` with an intended `role`. It includes `status` (e.g., pending, accepted, revoked), `expiresAt`, and `inviterId` for traceability. + +On acceptance, an invitation creates a corresponding membership if one does not already exist. + +## Relationships and constraints + + + + Users and organizations are related many-to-many through memberships. A user + can join multiple organizations; an organization has multiple members. + + + + We keep `organization.slug` unique across the system to ensure + consistent routing and discoverability. Within a single organization, each + `userId` should only appear once in memberships; enforce this + at the application layer or with a composite unique index + `(organizationId, userId)`. + + + + * Deleting an organization removes its dependent memberships and invitations. + * Deleting a user removes their memberships and invitations. + + These cascades preserve referential integrity and prevent orphaned records. + + + +## Tenancy and isolation + +### Tenant separator + +`organizationId` is the tenant key. All tenant-scoped data should either live under the organization or reference it directly. Every read/write path in the application should be constrained by the current `organizationId`. + +### Query guardrails + +Derive the active `organizationId` from authenticated context (session or URL slug → lookup → id). Apply `organizationId` filters at the repository/service layer to avoid cross‑tenant reads. Add composite indexes that include `organizationId` on frequently queried relations. + +### Isolation level + +All organizations share the same database and schema, separated by `organizationId`. This keeps operations simple and cost‑effective. If stricter isolation is needed, evolve toward schema‑per‑tenant or database‑per‑tenant with care, as operational overhead increases. + + + The term "organizations" is used throughout the starter kit to identify a group of users. However, depending on your application's needs, you might want to represent these groups with a different name, such as "Teams" or "Workspaces." + + If that's the case, we suggest retaining "organization" as the internal term within your codebase (to avoid the complexity of renaming it everywhere), while customizing the UI labels to your preferred terminology. To do this, simply update all user-facing instances of "Organization" in your interface to reflect the term that best fits your application. + + +## Lifecycle flows + +* **Create organization**: Create an organization (with `name`, `slug`, optional `logo`/`metadata`) and immediately create a membership for the creator with an elevated role (commonly `owner`). +* **Invite member**: + 1. Admin creates an invitation specifying `email` and intended `role`. + 2. The invite is sent by email with an expiring token. + 3. On acceptance, if the user exists they are added as a member; otherwise they register and then join. + 4. Handle idempotency so repeated accepts don’t duplicate memberships. +* **Leave or remove**: Members can leave an organization and admins can remove members. The policy that "at least one owner must remain" is enforced at the application layer. + + +--- +url: /docs/web/organizations/invitations +title: Invitations +description: Send, track, and accept organization invites. +--- + +You can invite teammates **by email** to join an organization straight from your organization settings. + +Acceptance is frictionless: we verify the invite, create (or reuse) the membership with the intended role, and activate the organization in the user's session. + +The implementation is based on the [Better Auth plugin](https://www.better-auth.com/docs/plugins/organization) and designed to drive engagement, minimize back-and-forth and keep admins in control. + +![Invitations list](/images/docs/web/organizations/invitations/list.png) + +## Model + +As we can see inside our [data model](/docs/web/organizations/data-model), an invitation targets an `email`, carries the intended `role`, records the `inviterId`, and is scoped to an `organizationId`. + +```ts +export const invitation = pgTable("invitation", { + id: text().primaryKey(), + organizationId: text() + .notNull() + .references(() => organization.id, { onDelete: "cascade" }), + email: text().notNull(), + role: text(), + status: text().default("pending").notNull(), + inviterId: text() + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + createdAt: timestamp().defaultNow().notNull(), + expiresAt: timestamp().notNull(), +}); +``` + +The invitations expire at `expiresAt` to keep links short‑lived. + +## Status + +An invitation can be in one of three states: + +* **Pending**: created/sent, awaiting acceptance. +* **Accepted**: verified; membership created or reused. +* **Rejected**: manually invalidated or removed via cascades. + + + Expiration is controlled by `expiresAt` (not a separate status). After this timestamp, the link is invalid and should be resent. + + +## Flow + +1. Admin creates an invite with `email` and `role`. The `organizationId` is inferred from the context. +2. System generates a signed, single-use token bound to the invite and `expiresAt` and sends a CTA link. +3. Recipient opens the link; we verify the token and email. +4. On success, we proceed to acceptance. + +## Onboarding + +### Existing user + +After verification, we create (or reuse) a membership with the invited role and set the active organization in the session. +![Join organization prompt](/images/docs/web/organizations/invitations/join.png) + +### New user + +We attach the invite context to signup; after registration, we create the membership and activate the organization - no detours required. +![Invitation disclaimer](/images/docs/web/organizations/invitations/sign-in-disclaimer.png) + +You can fully customize the invitation flow to fit your organization's needs. For example, you can add extra onboarding steps, capture additional user information, or implement advanced verification logic as part of the invite process. + +The system is designed to be extensible—tailor it to match your team's requirements and user experience preferences. + +## Automatic invalidation + +An invitation is automatically revoked in the following scenarios: + +* **The user accepts the invitation:** Once accepted, the token becomes invalid. +* **The user changes their email address:** To prevent misuse, any changes to the associated email automatically invalidate the token. +* **The user deletes their account:** Invitations linked to a deleted account are revoked to maintain data integrity. + +This ensures that invitations remain secure and aligned with the current state of user accounts. + +## Invitation management + +Admins of the organization and [super admins](/docs/web/admin/overview) can manage invitations via a dedicated section in the dashboard, where they can: + +* View the status of all invitations (`pending`, `accepted`, `rejected`). +* Resend invitations who did not respond. +* Revoke invitations if they were sent to the wrong email or are no longer needed. +* Adjust the role of an invitation if not yet accepted + + +--- +url: /docs/web/organizations/overview +title: Overview +description: Learn how to use organizations/teams/multi-tenancy in TurboStarter. +--- + +Organizations let you build teams and multi-tenant SaaS out of the box, which is a widely used pattern, especially in a [B2B](https://en.wikipedia.org/wiki/Business-to-business) apps. Users can create organizations, invite teammates, assign roles, and seamlessly switch between workspaces. + + + [Multi-tenancy](https://www.ibm.com/think/topics/multi-tenant) is a software architecture pattern where a single instance of an application serves multiple tenants, each with its own data and configuration. + + +The feature is mostly powered by the [Better Auth organization plugin](https://www.better-auth.com/docs/plugins/organization) and integrates with TurboStarter's API, routing, data layer, and UI components. This allows you to share most of the code between the web app, [mobile app](/docs/mobile/organizations/overview), and [extension](/docs/extension/organizations). + + + +## Architecture + +TurboStarter uses a pragmatic multi-tenant architecture: + +* **Tenant context** lives in the session as the active organization ID (derived from the user's selection or defaults). Server handlers read this context to enforce scoping. +* **Data scoping** is performed via `organizationId` on tenant-owned tables and guard clauses in queries. Background tasks and API routes receive the same context. +* **Authorization** combines tenant scoping with role checks. We separate “can access this tenant?” from “can perform this action within the tenant?”. +* **Extensibility**: add new tenant-bound entities by including `organizationId` and using the provided helpers to read the active organization. + +This keeps data isolated per organization while remaining simple to reason about and customize. + + + You can restrict who can create organizations, perform actions within it, and hook into + lifecycle events using our API. + + Check dedicated [Data model](/docs/web/organizations/data-model), [RBAC](/docs/web/organizations/rbac) and [Invitations](/docs/web/organizations/invitations) sections or direct [Better Auth docs](https://www.better-auth.com/docs/plugins/organization) for more details. + + +## Concepts + +To effectively use multi-tenancy in your app, we introduced a few core concepts that define how the whole system works: + +| Concept | Description | +| ----------------------- | ----------------------------------------------------------------------------------------------- | +| **Organization** | A workspace that owns resources and settings, acting as an isolated tenant. | +| **Member** | A user assigned to an organization. | +| **Role** | Access level within an organization (see [RBAC](/docs/web/organizations/rbac)). | +| **Invitation** | Email request to join an organization (see [Invitations](/docs/web/organizations/invitations)). | +| **Active organization** | The currently selected organization in a user's session, used to scope data and permissions. | + +These concepts provide the building blocks for flexible team management and secure, multi-tenant SaaS applications. + +## Development data + +In development, TurboStarter automatically [seeds](/docs/web/installation/commands#seeding-database) some example data when you [setup services](/docs/web/installation/commands#setting-up-services): + +* One organization is created by default. +* All default roles are created and assigned within that organization. +* Sample invitations are generated so you can test the invite flow. + +You can safely experiment with these sample organizations, roles, and invitations to understand multi-tenancy features - [reset](/docs/web/installation/commands#resetting-database) or [reseed](/docs/web/installation/commands#seeding-database) anytime to return to the default state. + +The default credentials for demo users can be customized using the `SEED_EMAIL` and `SEED_PASSWORD` environment variables. + + + The default development data and setup are intended for local development and + testing only. **Never** use these seeds or configurations in a production + environment - they are insecure and may expose sensitive functionality. + + +## Customization + +You have flexibility to adapt organizations to fit your product. For example, you might rename labels (such as Organization to *Team* or *Workspace*), and update the UI copy accordingly. + +You can adjust the available [roles and permissions](/docs/web/organizations/rbac) to suit your access model. + +The [invitation flow](/docs/web/organizations/invitations) can be customized, including how verification, onboarding, or metadata capture work. + +You may also want to introduce tenant-specific policies, like usage limits, feature flags, or billing rules. + +Feel free to check how to configure all of these features in the dedicated sections below. + + +--- +url: /docs/web/organizations/rbac +title: RBAC (Roles & Permissions) +description: Manage roles, permissions, and access scopes. +--- + +Role-based access control (RBAC) lets you define who can do what in an organization. + + + If you're new to the RBAC concept, a simple mental model is: + + * Users belong to organizations. + * Users get roles. + * Roles map to permissions on resources. + + +In TurboStarter, we primarily rely on the [Better Auth plugin](https://www.better-auth.com/docs/plugins/organization) for the heavy lifting - roles, permissions, teams, and member management - while handling critical logic with our own code. + +This provides a flexible access control system, letting you control user access based on their role in the organization. You can also define custom permissions per role. + + + TurboStarter ships with the default RBAC system configured out of the box. This setup may be enough if you're not planning a very complex access control system, but you can also easily customize it to your needs. + + It also includes [protecting routes](/docs/web/api/protected-routes) that users with specific roles can access by adding custom middlewares and disabling certain actions in the UI. + + +## Roles + +Roles are named bundles of permissions. Keep them few and well-defined. By default, we have the following roles: + +```ts +const MemberRole = { + MEMBER: "member", + ADMIN: "admin", + OWNER: "owner", +} as const; +``` + +A user can have multiple roles in an organization. For example, a user can be a member and an admin (if it makes sense for your application). + + + The organization's `admin` role is **different** from the user's global `admin` role. + + The organization `admin` governs permissions only inside the organization, whereas the global `admin` controls access to the [super admin dashboard](/docs/web/admin/overview). + + +To create additional roles with custom permissions, see the [official documentation](https://www.better-auth.com/docs/plugins/organization#create-access-control) for more details. + +## Permissions + +Permissions represent what actions a role can perform on which resources. To check if the current user has permission to perform an action, you can use the `hasPermission` function. + +```ts +const canCreateProject = await authClient.organization.hasPermission({ + permissions: { + project: ["create"], + }, +}); +``` + +Or, if you're performing the check on the server, you can use the `hasPermission` function from the `auth.api` object. + +```ts +await auth.api.hasPermission({ + headers: await headers(), + body: { + permissions: { + project: ["create"], // This must match the structure in your access control + }, + }, +}); +``` + +Once your roles and permissions are defined, you can avoid server checks (e.g., to reduce API calls) by using the client-side `checkRolePermission` function. + +```ts +const { activeMember } = useActiveOrganization(); + +const canUpdateProject = authClient.organization.checkRolePermission({ + permission: { + project: ["update"], + }, + role: activeMember.role, +}); +``` + +We leverage the existing custom hook to retrieve the active member role within the [active organization](/docs/web/organizations/active-organization) context. That way, you can easily check whether a member has permission to perform an action without a server round trip. + + + This does not include any dynamic roles or permissions because everything runs synchronously on the client-side. Use the `hasPermission` APIs to include checks for dynamic roles and permissions. + + +If you need to add more granular permissions to existing roles, or create new ones, use the [`createAccessControl`](https://www.better-auth.com/docs/plugins/organization#custom-permissions) API. + +For further customization - such as dynamic access control, lifecycle hooks, or team management - see the guidance in the [official documentation](https://www.better-auth.com/docs/plugins/organization). + + +--- +url: /docs/web/recipes/supabase +title: Supabase +description: Learn how to set up Supabase for your TurboStarter project. +--- + +[Supabase](https://supabase.com) is an open-source backend platform built on top of PostgreSQL that provides a managed database, storage, and other features out of the box. + +You can adopt Supabase incrementally - start with just the pieces you need (for example, database only, or database + storage) and add more features over time. There's no requirement to integrate everything at once. + +In this guide, we'll walk you through the process of setting up Supabase as a provider for your TurboStarter project. This could include using it as a [database](https://supabase.com/docs/guides/database), [storage](https://supabase.com/docs/guides/storage), [edge runtime for your API](https://supabase.com/docs/guides/functions) and more. + +## Prerequisites + +Before you start, make sure you have: + +* **TurboStarter project** cloned locally with dependencies installed (you can use our [CLI](/docs/web/cli) to create a new project in seconds) +* **Supabase account** - you can create one at [supabase.com](https://supabase.com/sign-up) +* Basic familiarity with the core database docs: + * [Database overview](/docs/web/database/overview) + * [Migrations](/docs/web/database/migrations) + * [Database client](/docs/web/database/client) + + + + ## Create a new Supabase project + + 1. Go to the [Supabase dashboard](https://supabase.com). + 2. Create a **new project** (choose a strong database password and a region close to your users). + 3. Supabase will automatically provision a **PostgreSQL database** for you. + + ![Create a new Supabase project](/images/docs/web/recipes/supabase/create-project.png) + + Optionally, you can customize the **Security options** by choosing the **Only Connection String** option - it will opt out of autogenerating API for tables inside your database. It's not needed for TurboStarter setup, but of course you can still leverage it for your custom use-cases. + + ![Security options](/images/docs/web/recipes/supabase/security-options.png) + + Once the project is ready, you can fetch the connection string. + + + + ## Get the database connection string + + In the Supabase dashboard: + + 1. Open your project. + 2. Click on the **Connect** button at the top. + 3. Locate the **connection string** for your chosen ORM (it will be under the **ORMs** tab). + + ![Connect application](/images/docs/web/recipes/supabase/connect-app.png) + + Copy this value - you'll use it as your `DATABASE_URL`. + + + In your Supabase connection string, you can see a placeholder like `[YOUR-PASSWORD]`. Make sure to replace this with the actual password you set when creating your Supabase project. + + + + + ## Configure environment variables + + TurboStarter reads database connection settings from the **root** `.env.local` file and uses them inside the `@turbostarter/db` package. + + Create (or update) the `.env.local` file in the **monorepo root**: + + ```dotenv title=".env.local" + DATABASE_URL="postgres://postgres.[YOUR-PROJECT-REF]:[YOUR-PASSWORD]@aws-0-[aws-region].pooler.supabase.com:6543/postgres?pgbouncer=true&connection_limit=1" + ``` + + Replace: + + * `YOUR-PROJECT-REF` with your Supabase project ref + * `YOUR-PASSWORD` with the database password you set when creating the project + * `aws-region` with the region shown in the Supabase connection string + + + These variables are validated in the `@turbostarter/db` package and used to create Drizzle client for your database. + + + For more background on how `DATABASE_URL` is used, see [Database overview](/docs/web/database/overview). + + + + ## Setup your Supabase database + + With `DATABASE_URL` now pointing to Supabase, you can apply the existing TurboStarter schema to your Supabase database. + + From the monorepo root, run: + + ```bash + pnpm with-env pnpm --filter @turbostarter/db db:migrate + ``` + + This will: + + * Use your Supabase `DATABASE_URL` from `.env.local` + * Run all pending SQL migrations from `packages/db/migrations` + * Create the full TurboStarter schema (users, billing, demo tables, etc.) in Supabase + + If you're actively iterating on the schema, you can generate new migrations and apply them as described in [Migrations](/docs/web/database/migrations). + + + After running your migrations, you may want to seed your database with initial data (such as demo users or organizations). You can do this by running the following command: + + ```bash + pnpm with-env pnpm turbo db:seed + ``` + + This will populate your Supabase database with some example data you can use to test your application. + + + + + ## Use Supabase Storage as S3-compatible storage + + TurboStarter's storage layer is designed to work seamlessly with **any S3-compatible provider**. In this section, we'll show how to use [Supabase Storage](/docs/web/storage/overview) as your application's file storage back-end. + + Supabase Storage provides a simple, S3-compatible API and is a great choice if you're already using Supabase for your database. + + ### Create a storage bucket + + 1. In the Supabase dashboard, go to **Storage → Buckets**. + 2. Click **Create bucket** (name it whatever you want, for example `avatars` or `uploads`). + 3. Adjust settings based on your needs (e.g. limit the maximum file size, specify the allowed file types, etc.) + + ![Create a new bucket](/images/docs/web/recipes/supabase/create-bucket.png) + + You can create multiple buckets (for documents, images, videos, etc.) if needed. + + ### Generate S3 access keys in Supabase dashboard + + 1. Go to **Storage → S3 → Access keys**. + 2. Click **New access key**. + 3. Give it a descriptive name and create the key. + 4. Copy the **Access key ID** and **Secret access key** to use in your application. + + ![Generate S3 access keys](/images/docs/web/recipes/supabase/s3-keys.png) + + ### Configure S3 environment variables for Supabase Storage + + In your weba application's `.env.local`, add (or update) the S3 configuration used by TurboStarter's storage layer: + + ```dotenv title=".env.local" + S3_REGION="us-east-1" + S3_BUCKET="avatars" + S3_ENDPOINT="https://[YOUR-PROJECT-REF].supabase.co/storage/v1/s3" + S3_ACCESS_KEY_ID="your-access-key-id" + S3_SECRET_ACCESS_KEY="your-secret-access-key" + ``` + + These variables integrate directly with the storage configuration described in: + + * [Storage overview](/docs/web/storage/overview) + * [Storage configuration](/docs/web/storage/configuration) + + Once set, existing TurboStarter file upload flows (e.g. user avatars, organization logos) will use Supabase Storage via presigned URLs. + + + + ## Run your API on Supabase Edge Functions + + As we're using a [Hono](https://hono.dev) as our API server, you can deploy it as a Supabase Edge Function so it runs close to your users. + + At a high level: + + 1. Install the [Supabase CLI](https://supabase.com/docs/guides/cli) and initialize a Supabase project locally with `supabase init`. + 2. Create a new [Edge Function](https://supabase.com/docs/guides/functions/quickstart) (for example `hono-backend`) with `supabase functions new hono-backend`. + 3. Inside the generated function (for example `supabase/functions/hono-backend/index.ts`), set up a basic Hono app and export it via `Deno.serve(app.fetch)`: + + ```ts + import { Hono } from "jsr:@hono/hono"; + + // change this to your function name + const functionName = "hono-backend"; + const app = new Hono().basePath(`/${functionName}`); + + app.get("/hello", (c) => c.text("Hello from hono-server!")); + + Deno.serve(app.fetch); + ``` + + 4. Run the function locally with `supabase start` and `supabase functions serve --no-verify-jwt`, then call it from your TurboStarter app using the local or deployed function URL. + 5. When you're ready, deploy the function with `supabase functions deploy` (or `supabase functions deploy hono-backend`) and manage it using the Supabase dashboard, as described in the [Supabase Edge Functions docs](https://supabase.com/docs/guides/functions). + + This is entirely optional, but it's a great fit for lightweight APIs, webhooks, and other serverless logic you want to run alongside your Supabase project. + + + + ## Explore additional Supabase features + + Supabase is a full Postgres development platform, so beyond the database and storage pieces wired up above you can gradually add more features as your app grows ([see the Supabase homepage](https://supabase.com/) for an overview). + + Some features that fit especially well with TurboStarter's design are: + + * [Realtime](https://supabase.com/docs/guides/realtime) - built on [Postgres replication](https://www.postgresql.org/docs/current/runtime-config-replication.html), so you can stream changes from your existing TurboStarter tables (inserts, updates, deletes) into live UIs without changing how you manage schema or RLS. You still define tables and policies via `@turbostarter/db`, and opt into Realtime on top. + * [Vector](https://supabase.com/docs/guides/vector) - powered by the [pgvector](https://github.com/pgvector/pgvector) extension and stored in regular Postgres tables, making it easy to integrate semantic search or AI features while keeping everything in the same migrations and Drizzle models you already use in TurboStarter. We're using it extensively in our dedicated [AI Kit](/ai). + * [Cron](https://supabase.com/docs/guides/functions/cron) - enables you to schedule background jobs and periodic tasks with [pg\_cron](https://github.com/citusdata/pg_cron). You can define cron jobs for things like scheduled database cleanups, sending emails, report generation, or any recurring logic, all managed alongside your TurboStarter app with full Postgres integration. + + Because these features are all layered on top of Postgres, you can introduce them incrementally and keep managing everything through your familiar workflow. + + + + ## Start the development server + + With the database and other services configured to use Supabase, you can start TurboStarter as usual from the monorepo root: + + ```bash + pnpm dev + ``` + + TurboStarter will now: + + * Use **Supabase Postgres** as your database through `DATABASE_URL` + * Use **Supabase Storage** as your file storage through the S3-compatible endpoint + * Leverage **Supabase Edge Functions** (for example, with Hono) for your serverless backend + + + +That's it! You can now start building your application with Supabase as your main provider. Explore the [Supabase documentation](https://supabase.com/docs) for more features and best practices. + + +--- +url: /docs/web/stack +title: Tech Stack +description: A detailed look at the technical details. +--- + +## Turborepo + +[Turborepo](https://turbo.build/) is a monorepo tool that helps you manage your project's dependencies and scripts. We chose a monorepo setup to make it easier to manage the structure of different features and enable code sharing between different packages. + +} /> + +## Next.js + +[Next.js](https://nextjs.org) is one of the most popular [React](https://react.dev) frameworks that enables server-side rendering, static site generation, and more. We chose Next.js for its flexibility and ease of use. We're also using it to host our serverless API. + + + } /> + + } /> + + +## Hono & React Query + +[Hono](https://hono.dev) is a small, simple, and ultrafast web framework for the edge. It provides tools to help you build APIs and web applications faster. It includes an RPC client for making type-safe function calls from the frontend. We use Hono to build our serverless API endpoints. + +To make data fetching and caching from our API easy and reliable, we pair Hono with [React Query](https://tanstack.com/query/latest). It helps manage asynchronous data, caching, and state synchronization between the client and backend, delivering a fast and seamless UX. + + + } /> + + + } + /> + + +## Better Auth + +[Better Auth](https://www.better-auth.com) is a modern authentication library for fullstack applications. It provides ready-to-use snippets for features like email/password login, magic links, OAuth providers, and more. We use Better Auth to handle all authentication flows in our application. + +} /> + +## Tailwind CSS + +[Tailwind CSS](https://tailwindcss.com) is a utility-first CSS framework that helps you build custom designs without writing any CSS. We also use [Radix UI](https://radix-ui.com) for our headless components library and [shadcn/ui](https://ui.shadcn.com), which enables you to generate pre-designed components with a single command. + + + } /> + + } /> + + } /> + + +## Drizzle + +[Drizzle](https://orm.drizzle.team/) is a super fast [ORM](https://orm.drizzle.team/docs/overview) (Object-Relational Mapping) tool for databases. It helps manage databases, generate TypeScript types from your schema, and run queries in a fully type-safe way. + +We use [PostgreSQL](https://www.postgresql.org) as our default database, but thanks to Drizzle's flexibility, you can easily switch to MySQL, SQLite or any [other supported database](https://orm.drizzle.team/docs/connect-overview) by updating a few configuration lines. + + + } /> + + } /> + + + +--- +url: /docs/web/storage/configuration +title: Configuration +description: Learn how to configure storage in TurboStarter. +--- + +Currently, TurboStarter supports all S3-compatible storage providers, including [AWS S3](https://aws.amazon.com/s3/), [DigitalOcean Spaces](https://www.digitalocean.com/products/spaces), [Cloudflare R2](https://www.cloudflare.com/products/r2/), and [Supabase Storage](https://supabase.com/storage). + +For a concrete example using Supabase Storage as an S3-compatible provider, see the [Supabase recipe](/docs/web/recipes/supabase#use-supabase-storage-as-s3-compatible-storage). + +The setup process is straightforward - you just need to configure a few environment variables in both your local environment and hosting provider: + +```dotenv +S3_REGION= +S3_BUCKET= +S3_ENDPOINT= +S3_ACCESS_KEY_ID= +S3_SECRET_ACCESS_KEY= +``` + +Let's break down each required variable: + +* `S3_REGION`: The [AWS region](https://aws.amazon.com/about-aws/global-infrastructure/regions_az/) where your storage is located - defaults to `us-east-1` +* `S3_BUCKET`: The default name of your storage bucket - you can pass different for each request +* `S3_ENDPOINT`: The S3 [endpoint URL](https://docs.aws.amazon.com/general/latest/gr/s3.html) for your storage provider - defaults to `https://s3.amazonaws.com` +* `S3_ACCESS_KEY_ID`: Your storage provider's [access key ID](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html) +* `S3_SECRET_ACCESS_KEY`: Your storage provider's [secret access key](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html) + +You can learn more about S3 service configuration in the [official AWS documentation](https://docs.aws.amazon.com/AmazonS3/latest/API/Welcome.html) or your specific storage provider's documentation. + + +--- +url: /docs/web/storage/managing-files +title: Managing files +description: Learn how to manage files in TurboStarter. +--- + +Before you start managing files, make sure you have [configured storage](/docs/web/storage/configuration). + +## Permissions + +Most S3-compatible storage providers allow you to configure bucket permissions and access policies. It's crucial to properly set these up to secure your files and control who can access them. + +Here are some key security recommendations: + +* Keep your bucket private by default +* Use IAM roles and policies to manage access +* Enable server-side encryption for sensitive data +* Configure CORS settings appropriately for client-side uploads +* Regularly audit bucket permissions and access logs + +Making your bucket public is strongly discouraged as it can expose sensitive data and lead to unauthorized access and unexpected costs from bandwidth usage. + +For detailed guidance on configuring bucket policies and permissions, refer to your storage provider's documentation: + +* [AWS S3 Security Documentation](https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-policy-language-overview.html) +* [DigitalOcean Spaces Security](https://docs.digitalocean.com/products/spaces/how-to/manage-access/) +* [Cloudflare R2 Security](https://developers.cloudflare.com/r2/api/s3/tokens/) +* [Supabase Storage Security](https://supabase.com/docs/guides/storage/security/access-control) + +## Uploading files + +As explained in the [overview](/docs/web/storage/overview), TurboStarter uses presigned URLs to upload files to your storage provider. + +We prepared a special endpoint to generate presigned URLs for your uploads to use in your client-side code. + +```ts title="storage/router.ts" +export const storageRouter = new Hono().get( + "/upload", + enforceAuth, + validate("query", getObjectUrlSchema), + async (c) => c.json(await getUploadUrl(c.req.valid("query"))), +); +``` + + + The signed URL is only valid for a limited time and will work for anyone who has access to it during that period. Make sure to handle the URL securely and avoid exposing it to unauthorized users. + + +Then, you can use it to upload files to the generated presigned URL from your frontend code: + +```tsx title="upload.tsx" +const upload = useMutation({ + mutationFn: async (data: { file?: File }) => { + const extension = data.file?.type.split("/").pop(); + const path = `files/${crypto.randomUUID()}.${extension}`; + + const { url: uploadUrl } = await handle(api.storage.upload.$get)({ + query: { path }, + }); + + const response = await fetch(uploadUrl, { + method: "PUT", + body: data.file, + headers: { + "Content-Type": data.file?.type ?? "", + }, + }); + + if (!response.ok) { + throw new Error("Failed to upload file!"); + } + }, + onError: (error) => { + toast.error(error.message}); + }, + onSuccess: async ({ publicUrl, oldImage }, _b, context) => { + toast.success("File uploaded!"); + }, + }); +``` + +The code above demonstrates how to implement file uploads in your application: + +1. First, we have a server-side endpoint (`storageRouter`) that generates presigned URLs for uploads. This endpoint: + * [Requires authentication](/docs/web/api/protected-routes) via `enforceAuth` + * Validates the request parameters using `validate` + * Returns a presigned URL for uploading + +2. Then, in the frontend code (`upload.tsx`), we use React Query's `useMutation` hook to handle the upload process: + * Requests a presigned URL from the server + * Uploads the file directly to the storage provider using the presigned URL + * Handles success and error cases with toast notifications + +This approach ensures secure file uploads while avoiding server bandwidth costs and function timeout issues. + +### Public uploads + +Although **it's not recommended** to use public uploads in production, you can use the same endpoint to generate presigned URLs for public uploads: + +```ts title="storage/router.ts" +export const storageRouter = new Hono().get( + "/upload", + validate("query", getObjectUrlSchema), + async (c) => c.json(await getUploadUrl(c.req.valid("query"))), +); +``` + +Just remove the `enforceAuth` middleware from the endpoint and keep rest of the logic the same. + +## Displaying files + +We provide dedicated endpoints for retrieving signed URLs specifically for displaying files. These URLs are time-limited to maintain security, so they cannot be used for permanent storage or long-term access: + +```ts title="storage/router.ts" +export const storageRouter = new Hono().get( + "/signed", + enforceAuth, + validate("query", getObjectUrlSchema), + async (c) => c.json(await getSignedUrl(c.req.valid("query"))), +); +``` + +This endpoint is perfect for displaying files that should only be accessible to authorized users for a limited time. + +### Public files + +For displaying files publicly (without authorization and time limitations), you can use the `/public` endpoint: + +```ts title="storage/router.ts" +export const storageRouter = new Hono().get( + "/public", + validate("query", getObjectUrlSchema), + async (c) => c.json(await getPublicUrl(c.req.valid("query"))), +); +``` + +This endpoint generates a public URL for the file that you can use to display in your application. Please ensure that your bucket policy allows public access to the files and verify that you're not exposing any sensitive information. + +## Deleting files + +Deleting files works almost the same way as uploading files. You just need to generate a presigned URL for deletion and then use it to remove the file: + +```ts title="storage/router.ts" +export const storageRouter = new Hono().get( + "/delete", + validate("query", getObjectUrlSchema), + async (c) => c.json(await getDeleteUrl(c.req.valid("query"))), +); +``` + +Then, in the frontend code, we use React Query's `useMutation` hook to handle the deletion process: + +```tsx title="delete.tsx" +const remove = useMutation({ + mutationFn: async () => { + const path = file.split("/").pop(); + if (!path) return; + + const { url: deleteUrl } = await handle(api.storage.delete.$get)({ + query: { path: `files/${path}` }, + }); + + await fetch(deleteUrl, { + method: "DELETE", + }); + }, + onError: (error) => { + toast.error(error.message); + }, + onSuccess: () => { + toast.success("File removed!"); + }, +}); +``` + +Now that you understand how to manage files in TurboStarter, it's time to build something awesome! Try creating a file upload component, building a photo gallery, or implementing a document management system. + + +--- +url: /docs/web/storage/overview +title: Overview +description: Get started with storage in TurboStarter. +--- + +With TurboStarter, you can easily upload and manage files (images, videos, documents, and more) in your application. + +Currently, all S3-compatible storage providers are supported, including [AWS S3](https://aws.amazon.com/s3/), [DigitalOcean Spaces](https://www.digitalocean.com/products/spaces), [Cloudflare R2](https://www.cloudflare.com/products/r2/), [Supabase Storage](https://supabase.com/storage), and others. + +If you're using Supabase, you can follow the [Supabase recipe](/docs/web/recipes/supabase#use-supabase-storage-as-s3-compatible-storage) for a concrete example of configuring Supabase Storage as your S3-compatible backend. + +## Uploading files + +The most common approach to uploading files is to use client-side uploads. With client-side uploads, you avoid paying ingress/egress fees for transferring file binary data through your server. + +Additionally, most hosting platforms like [Vercel](https://vercel.com/docs/functions/runtimes#size-limits) or [Netlify](https://answers.netlify.com/t/what-is-the-maximum-file-size-upload-limit-in-a-netlify-form-submission/108419) have limitations on file size and maximum serverless function execution time. + +That's why TurboStarter utilizes the **presigned URLs** feature of storage providers to upload files. Instead of sending files to the serverless function, the client requests a time-limited presigned URL from the serverless function and then uploads the file directly to the storage provider. + + + +1. Client **requests** a presigned URL from the serverless function. +2. Server parses the request, validates the payload, optionally saves the metadata, and **returns the presigned URL** to the client. +3. Client **uploads the file** to the presigned URL within the expiration time. +4. (Optional) Once the file is uploaded, the serverless function is notified about the upload event, and the file metadata is saved to the database. + + + This approach ensures that credentials remain secure, handles authorization and authentication properly, and avoids the limitations of serverless platforms. + + +The configuration and use of storage is straightforward and simple. We'll explore this in more detail in the following sections. + + +--- +url: /docs/web/tests/e2e +title: E2E tests +description: Simulate real user scenarios across the entire stack with automated end-to-end test tools and examples. +--- + + + End-to-end (E2E) tests will be available soon, allowing you to automate testing of real user flows and interactions across your application. + + Stay tuned for updates as we roll out robust E2E testing resources and examples. + + [See roadmap](https://github.com/orgs/turbostarter/projects/1) + + + +--- +url: /docs/web/tests/unit +title: Unit tests +description: Write and run fast unit tests for individual functions and components with instant feedback. +--- + +Unit tests are a type of automated test where individual units or components are tested. The "unit" in "unit test" refers to the smallest testable parts of an application. These tests are designed to verify that each unit of code performs as expected. + +TurboStarter uses [Vitest](https://vitest.dev) as the unit testing framework. It's a blazing-fast test runner built on top of [Vite](https://vitejs.dev), designed for modern JavaScript and TypeScript projects. + + + If you've used [Jest](https://jestjs.io) before, you already know Vitest - it shares the same API. But Vitest is built for speed: native TypeScript support without transpilation, parallel test execution, and a smart watch mode that only re-runs tests affected by your changes. + + It comes with everything you need out of the box - code coverage, snapshot testing, mocking, and a slick UI for debugging. Fast feedback, zero configuration. + + +## Why write unit tests? + +Unit tests give you **fast, focused feedback** on small pieces of your code - individual functions, hooks, or components. Instead of debugging an entire page or flow, you can verify just the logic you care about in isolation. + +They also act as **living documentation**: a good test tells you how a function is supposed to behave, which edge cases are important, and what assumptions the code makes. This makes it much easier to safely refactor or extend features later. + +In TurboStarter, unit tests are designed to be **cheap and quick to run**, so you can keep Vitest running in watch mode while you code. Every change you make is immediately checked, helping you catch regressions before they ever reach integration or end‑to‑end tests. + +## Configuration + +TurboStarter configures Vitest to be **as simple as possible**, while still taking advantage of [Turborepo's caching](https://turborepo.com/docs/crafting-your-repository/caching) and Vitest's [Test Projects](https://vitest.dev/guide/projects). + +```ts title="vitest.config.ts" +import { mergeConfig } from "vitest/config"; + +import baseConfig from "@turbostarter/vitest-config/base"; + +export default mergeConfig(baseConfig, { + test: { + /* your extended test configuration here */ + }, +}); +``` + +* **Per-package tests**: each package that has unit tests defines its own `test` script. This keeps the configuration close to the code and makes it easy to add tests to any workspace. +* **Turbo tasks for CI**: the root `test` task (`pnpm test`) uses `turbo run test` to execute all package-level test scripts with smart caching, which is ideal for CI pipelines where you want to avoid re-running unchanged tests. +* **Vitest Test Projects for local dev**: a root Vitest configuration uses [Test Projects](https://vitest.dev/guide/projects) to run all unit test suites from a single command, which is perfect for local development when you want fast feedback across the whole monorepo. + +This **hybrid setup** combines Turborepo and Vitest Projects in a way that fits TurboStarter's principles: cached, package-aware runs in CI, and a single, unified Vitest entry point for local development. + +You can read more about this setup in the official documentation guides listed below. + + + + + + + +## Running tests + +There are a few different ways to run unit tests, depending on what you're doing: + +* **CI / full test run** - at the root of the repo: + +```bash +pnpm test +``` + +This runs `turbo run test`, which executes all `test` scripts in packages that define them, with Turborepo handling caching so unchanged packages are skipped. This is what you should use in your CI/CD pipeline. + +* **One-off local run with Vitest Projects**: + +```bash +pnpm test:projects +``` + +This uses Vitest [Test Projects](https://vitest.dev/guide/projects) to run all configured unit test suites from a single command, which is great when you want to quickly validate the whole monorepo locally. + +* **Watch mode during development**: + +```bash +pnpm test:projects:watch +``` + +This starts Vitest in watch mode across all Test Projects. As you edit files, only the affected tests are re-run, giving you fast feedback while you work. + +## Code coverage + +Unit test coverage helps you understand **how much** of your code is being tested. While it can't guarantee bug-free code, it shines a light on untested paths that could hide issues or regressions. + +To generate a code coverage report for all unit tests, run: + +```bash +pnpm turbo test:coverage +``` + +This command runs the coverage task across all relevant packages (using Turborepo) and collects the results into a single coverage output. + +To open the coverage report in your browser: + +```bash +pnpm turbo test:coverage:view +``` + +This will build the HTML report and launch it using your default browser, so you can explore which files and branches are covered. + + + You can also store the generated coverage report as a [GitHub Actions artifact](https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts) during your CI/CD pipeline, just add the following steps to your workflow job: + + ```yaml title=".github/workflows/ci.yml" + # your workflow job configuration here + + - name: 📊 Generate coverage + run: pnpm turbo test:coverage + + - name: 🗃️ Archive coverage report + uses: actions/upload-artifact@v5 + with: + name: coverage-${{ github.sha }} + path: tooling/vitest/coverage/report + ``` + + This will generate a test coverage report and upload it as an artifact, so you can access it from GitHub Actions tab for later inspection. + + +A high coverage percentage means your tests execute most lines and branches - but the quality and relevance of your tests matter more than the raw number. Use coverage reports to spot gaps and guide improvements, not as the sole metric of test health. + +![Code coverage](/images/docs/code-coverage.png) + +## Best practices + +Unit tests should work **for you**, not the other way around. Focus on writing tests that make it easier to change code with confidence, not on satisfying arbitrary rules or reaching a magic number in a dashboard. + +Code coverage is a **useful metric**, but it **SHOULD NOT** be the goal. It's better to have a smaller set of high‑value tests that cover critical paths and edge cases than a huge suite of fragile tests that are hard to maintain. + +When in doubt, ask: *“Does this test give **me** confidence that I can change this code without breaking users?”* If the answer is no, refactor or remove it. + +Finally, keep unit tests focused on **small, isolated pieces of logic**. More advanced flows — like multi-step user journeys, cross-service interactions, or full-page behavior — are better covered by [end-to-end (E2E) tests](/docs/web/tests/e2e), where you can verify the system as a whole. + + +--- +url: /docs/web/troubleshooting/billing +title: Billing +description: Find answers to common billing issues. +--- + +## Checkout can't be created + +This happen in the following cases: + +1. The environment variables are not set correctly. Please make sure you have set the environment variables corresponding to your billing provider in `.env.local` if locally - or in your hosting provider's dashboard if in production +2. The price IDs used are incorrect. Make sure to use the exact price IDs as they are in the payment provider's dashboard. + +[Read more about billing configuration](/docs/web/billing/configuration) + +## Database is not updated after subscribing to a plan + +This may happen if the webhook is not set up correctly. Please make sure you have set up the webhook in the payment provider's dashboard and that the URL is correct. + +If working locally, make sure that: + +1. If using Stripe, that the Stripe CLI or configured proxy is up and running ([see the Stripe documentation for more information](/docs/web/billing/stripe#create-a-webhook)) +2. If using Lemon Squeezy, that the webhook set in Lemon Squeezy is correct and that the server is running. Additionally, make sure the proxy is set up correctly if you are testing locally ([see the Lemon Squeezy documentation for more information](/docs/web/billing/lemon-squeezy#create-a-webhook)). + + +--- +url: /docs/web/troubleshooting/deployment +title: Deployment +description: Find answers to common web deployment issues. +--- + +## Deployment build fails + +This is most likely an issue related to the environment variables not being set correctly in the deployment environment. Please analyse the logs of the deployment provider to see what is the issue. + +The kit is very defensive about incorrect environment variables, and will throw an error if any of the required environment variables are not set. In this way - the build will fail if the environment variables are not set correctly - instead of deploying a broken application. + +Check our guides for the most popular hosting providers for more information on how to deploy your TurboStarter project correctly: + + + + + + + + + + + + + + + + + +## What should I set as a URL before my first deployment? + +That's very good question! For the first deployment you can set any URL, and then, after you (or your provider) assign a domain name, you can change it to the correct one. There's nothing wrong with redeploying your project multiple times. + +## Sign in with OAuth provider doesn't work + +This is most likely a settings issues in the provider's settings. To troubleshoot this issue, follow these steps: + +1. **Verify provider settings**: Ensure that the OAuth provider's settings are correctly configured. Check that the client ID, client secret, and redirect URI are accurate and match the values in your application. +2. **Check environment variables**: Confirm that the environment variables for the OAuth provider are set correctly in your application production environment. +3. **Validate callback URLs**: Ensure that the callback URLs for each provider are set correctly and match the URLs in your application. This is crucial for the OAuth flow to work correctly. + +Please read [Better Auth documentation](https://www.better-auth.com/docs/concepts/oauth) for more information on how to set up third-party providers. + + +--- +url: /docs/web/troubleshooting/emails +title: Emails +description: Find answers to common emails issues. +--- + +## I want to use a different email provider + +Of course! You can use any email provider that you want. All you need to do is to implement the `EmailProviderStrategy` and export it in your `index.ts` file. + +[Read more about sending emails](/docs/web/emails/sending) + +## My emails are landing in the spam folder + +Emails landing in spam folders is a common issue. Here are key steps to improve deliverability: + +1. **Configure proper domain setup**: + * Use a dedicated subdomain for sending emails (e.g., mail.yourdomain.com) + * Ensure [reverse DNS (PTR) records](https://www.cloudflare.com/learning/dns/dns-records/dns-ptr-record/) are properly configured + * Warm up your sending domain gradually + +2. **Implement authentication protocols**: + * Set up [SPF records](https://www.cloudflare.com/learning/dns/dns-records/dns-spf-record/) to specify authorized sending servers + * Enable [DKIM signing](https://www.cloudflare.com/learning/dns/dns-records/dns-dkim-record/) to verify email authenticity + * Configure [DMARC policies](https://www.cloudflare.com/learning/dns/dns-records/dns-dmarc-record/) to prevent spoofing + +3. **Follow deliverability best practices**: + * Include clear unsubscribe mechanisms in all marketing communications + * Personalize content appropriately + * Avoid excessive promotional language and spam triggers + * Maintain consistent HTML formatting and styling + * Only include links to verified domains + * Keep a regular sending schedule + * Clean your email lists regularly + * Use double opt-in for new subscribers + +4. **Monitor and optimize**: + * Track key metrics like delivery rates, opens, and bounces + * Monitor spam complaint rates + * Review email authentication reports + * Test emails across different clients and devices + * Adjust sending practices based on performance data + + +--- +url: /docs/web/troubleshooting/installation +title: Installation +description: Find answers to common web installation issues. +--- + +## Cannot clone the repository + +Issues related to cloning the repository are usually related to a Git misconfiguration in your local machine. The commands displayed in this guide using SSH: these will work only if you have setup your SSH keys in Github. + +If you run into issues, [please make sure you follow this guide to set up your SSH key in Github.](https://docs.github.com/en/authentication/connecting-to-github-with-ssh) + +If this also fails, please use HTTPS instead. You will be able to see the commands in the repository's Github page under the "Clone" dropdown. + +Please also make sure that the account that accepted the invite to TurboStarter, and the locally connected account are the same. + +## My environment variables from `.env.local` file are not being loaded + +Make sure you are running the `pnpm dev` command from the root directory of your project (where the `pnpm-workspace.yaml` file is located) + +Also, ensure that the `.env.local` files are present in the apps that need them. For example, the `.env` file should be present in the `apps/web` directory for the web app. + + + TurboStarter uses the `dotenv-cli` to load environment variables from a `.env` files. The `dotenv-cli` is automatically used when running the `pnpm dev` command from the root directory. + + +## Next.js server doesn't start + +This may happen due to some issues in the packages. Try to clean the workspace using the following command: + +```bash +pnpm clean +``` + +Then, reinstall the dependencies: + +```bash +pnpm i +``` + +You can now retry running the dev server. + +## Local database doesn't start + +If you cannot run the local database container, it's likely you have not started [Docker](https://docs.docker.com/get-docker/) locally. Our local database requires Docker to be installed and running. + +Please make sure you have installed Docker (or compatible software such as [Colima](https://github.com/abiosoft/colima), [Orbstack](https://github.com/orbstack/orbstack)) and that is running on your local machine. + +Also, make sure that you have enough [memory and CPU allocated](https://docs.docker.com/engine/containers/resource_constraints/) to your Docker instance. + +## I don't see my translations + +If you don't see your translations appearing in the application, there are a few common causes: + +1. Check that your translation `.json` files are properly formatted and located in the correct directory +2. Verify that the language codes in your configuration match your translation files +3. Enable debug mode (`debug: true`) in your i18next configuration to see detailed logs + +[Read more about configuration for translations](/docs/web/internationalization/configuration) + +## "Module not found" error + +This issue is mostly related to either dependency installed in the wrong package or issues with the file system. + +The most common cause is incorrect dependency installation. Here's how to fix it: + +1. Clean the workspace: + + ```bash + pnpm clean + ``` + +2. Reinstall the dependencies: + ```bash + pnpm i + ``` + +If you're adding new dependencies, make sure to install them in the correct package: + +```bash +# For main app dependencies +pnpm install --filter web my-package + +# For a specific package +pnpm install --filter @turbostarter/ui my-package +``` + +If the issue persists, please check the file system for any issues. + +### Windows OneDrive + +OneDrive can cause file system issues with Node.js projects due to its file syncing behavior. If you're using Windows with OneDrive, you have two options to resolve this: + +1. Move your project to a location outside of OneDrive-synced folders (recommended) +2. Disable OneDrive sync specifically for your development folder + +This prevents file watching and symlink issues that can occur when OneDrive tries to sync Node.js project files. + + +--- +url: /ai/docs/agents +title: Agents +description: Build powerful, autonomous AI agents capable of performing complex tasks within your web and mobile applications. +--- + + + This feature is currently under development and will be + available in a future release. + + [See roadmap](https://github.com/orgs/turbostarter/projects/1) + + +The AI Agents demo will showcase how to create intelligent, autonomous agents capable of executing complex tasks within your web and mobile applications. + +These agents will leverage advanced AI techniques to interact with users, tools, and data sources. + +## Features + + + + Design agents once and deploy them seamlessly across multiple platforms + including React, React Native, Expo, and Next.js through a unified + architecture. + + + + Implement sophisticated context retention that allows agents to maintain + state and recall critical information across conversations and devices with + perfect continuity. + + + + Enable agents to take meaningful actions by integrating with external tools, + accessing APIs, and executing functions dynamically within secure, + controlled environments. + + + + Leverage the [Model Context + Protocol](https://modelcontextprotocol.io/introduction) to standardize + context delivery between agents and Large Language Models (LLMs). This + enables frictionless connections to diverse data sources and tools, + dramatically enhancing agent capabilities. + + + + Orchestrate complex workflows combining Retrieval-Augmented Generation + (RAG), tool utilization, and MCP server interactions to solve sophisticated + tasks that previously required human intervention. + + + +Stay tuned for the release of this exciting functionality! + + +--- +url: /ai/docs/chat +title: Chatbot +description: Build a powerful AI assistant with multiple LLMs, generative UI, web browsing, and image analysis. +--- + +The [Chatbot](https://ai.turbostarter.dev/chat) demo application showcases an advanced AI assistant capable of engaging in complex conversations, performing web searches, and understanding context. It integrates multiple large language models (LLMs) and allows users to attach files to the chat window. + + + +## Features + +The chatbot offers a variety of capabilities for an enhanced conversational experience: + + + + Switch effortlessly between leading AI providers like + [OpenAI](/ai/docs/openai) and [Anthropic](/ai/docs/anthropic) within a + single, consistent chat interface. + + + + Experience an AI that truly understands complex questions and delivers + thoughtful, nuanced responses based on comprehensive reasoning. + + + + Access up-to-the-minute information directly from the web through the + integrated search capability powered by [Tavily AI](https://tavily.com/). + + + + Enrich conversations by sharing and analyzing files, images, or web links + directly within the chat interface for contextual discussion. + + + + Enjoy natural, fluid conversations with responses that stream in real-time, + eliminating waiting periods. + + + + Seamlessly manage your conversation history with features to save, organize, + and revisit previous discussions. + + + +## Setup + +To implement your advanced AI assistant, you'll need several services configured. If you haven't set these up yet, start with: + + + + + + + +### AI models + + + Different models offer varying capabilities for tool calling, reasoning, and file processing. Consider these differences when selecting the optimal model for your specific use case. + + +The Chatbot leverages the AI SDK to support various language and vision models. You can easily switch between models based on your needs. Explore the documentation for the most popular models: + + + } /> + + } /> + + } /> + + } /> + + +For detailed configuration of specific providers and other supported models, refer to the [AI SDK documentation](https://sdk.vercel.ai/providers/ai-sdk-providers). + +### Web browsing + +The chatbot utilizes [Tavily AI](https://tavily.com/) to provide real-time web search capabilities. Tavily is a specialized search engine optimized for LLMs and AI agents, designed to deliver highly relevant search results by automatically handling the complexities of web scraping, filtering, and extracting relevant information. + +We selected Tavily because it dramatically simplifies the integration of current web data into AI applications through a single API call that returns comprehensive, AI-ready search results. + + + Tavily offers a generous free tier with [1,000 API credits per + month](https://docs.tavily.com/documentation/api-credits) without requiring + credit card information. A basic search consumes 1 credit, while an advanced + search uses 2 credits. Paid plans are available for higher volume usage. + + +To enable web browsing, follow these steps: + + + + #### Get Tavily API Key + + Sign up or log in at the [Tavily Platform](https://app.tavily.com/sign-in) to obtain your API key from the dashboard. + + + + #### Add API Key to Environment + + Add your API key to your project's `.env` file (e.g., in `apps/web`): + + ```bash title=".env" + TAVILY_API_KEY=tvly-your-api-key + ``` + + + +With the API key properly configured, the chatbot will automatically utilize Tavily for searches when contextually appropriate. + +## Data persistence + +User interactions and chat history are persisted to ensure a continuous experience across sessions. + + + +Conversation data is organized within a dedicated PostgreSQL schema named `chat` +to maintain clear separation from other application data. + +* `chats`: stores records for each conversation session, including essential metadata like user ID and creation timestamp. +* `messages`: maintains the content of individual messages exchanged within conversations, linked to their parent chat session. +* `parts`: handles complex message structures by breaking down content into smaller components, particularly useful for generative UI elements or multi-modal content. + + + +Files shared within conversations (such as images or documents) are uploaded to [cloud storage](/ai/docs/storage) (S3-compatible), with references to these attachments stored within the message content or parts. + +## Structure + +The Chatbot functionality is thoughtfully distributed across shared packages and platform-specific code for web and mobile, ensuring optimal code reuse and consistency. + +### Core + +The `@turbostarter/ai` package, located in `packages/ai`, contains the central chat functionality in the `src/chat` directory. It includes: + +* Essential constants, types, and validation schemas for chat interactions +* Core API logic for managing conversations and messages +* Comprehensive chat history persistence and retrieval functionality +* AI model provider configuration and initialization +* Integrations for external tools like web search + +### API + +Built with Hono, the `packages/api` package defines all API endpoints. Chat-specific routes are organized under `src/modules/ai/chat`: + +* `chat.router.ts`: establishes Hono RPC routes, handles input validation, and connects frontend requests to the core AI logic in `packages/ai` +* Manages authentication, request processing, and database interactions through the core package + +### Web + +The Next.js web application in `apps/web` implements the user-facing chat interface: + +* `src/app/[locale]/(apps)/chat/**`: contains the Next.js App Router pages and layouts dedicated to the chat experience +* `src/components/chat/**`: houses reusable React components for the chat interface (message bubbles, input area, model selector, etc.) + +### Mobile + +The Expo/React Native mobile application in `apps/mobile` delivers a native chat experience: + +* `src/app/chat/**`: defines the primary screens for the mobile chat interface +* `src/components/chat/**`: contains React Native components styled to match the web version, optimized for mobile interaction +* **API interaction**: utilizes the same Hono RPC client (`packages/api`) as the web app for consistent backend communication + +This modular structure promotes separation of concerns and facilitates independent development and scaling of different parts of the application. + + +--- +url: /ai/docs/image +title: Image Generation +description: Learn how to generate images using AI models within the TurboStarter AI demo application. +--- + +The [Image Generation](https://ai.turbostarter.dev/image) demo application allows users to create unique visuals from textual descriptions using various AI models. It provides a simple interface to input prompts, select models, and view generated images. + + + +## Features + +Explore the capabilities of the AI-powered image generation tool: + + + + Create images simply by describing what you want to see in text. + + + + Choose from different AI image generation models offered by various + providers. + + + + Select the desired aspect ratio for your generated images (e.g. square, + landscape, portrait). + + + + Create multiple design variations from a single prompt simultaneously, + accelerating your creative workflow. + + + + Access and reference your complete generation history, including all prompts + and resulting images for continued iteration. + + + +## Setup + +To implement image generation in your application, you'll need to configure the necessary backend services. + + + + + + + +You'll also need API keys for your preferred AI models. Follow the detailed setup instructions in the provider documentation linked below. + +## AI models + +The Image Generation app leverages the AI SDK to support various models capable of creating images from text. Configure the providers for the models you wish to use: + + + } /> + + } /> + + +For detailed implementation guidance, refer to the [AI SDK documentation](https://sdk.vercel.ai/docs/ai-sdk-core/image-generation) covering the `generateImage` function and supported providers. + +## Data persistence + +Details about image generation requests and the resulting images are stored to maintain user history. + + + +Data is organized within a dedicated PostgreSQL schema named `image`: + +* `generations`: captures detailed information about each generation request, including the `prompt`, selected `model`, `aspectRatio`, requested image `count`, `userId`, and precise timestamps. +* `images`: stores complete metadata for each generated image, linked to its parent `generation` record via `generationId` and maintaining the `url` reference to the stored image file. + + + +The generated image files are securely stored in [cloud storage](/ai/docs/storage) (S3-compatible). Each image's location is tracked via the `url` field in the `images` table for reliable retrieval. + +## Structure + +The Image Generation feature is architected across the monorepo for optimal code organization and reusability. + +### Core + +The `@turbostarter/ai` package (`packages/ai`) contains the essential logic under `modules/image`: + +* Comprehensive types, validation schemas (for prompts, aspect ratios, etc.), and constants +* Core API logic for processing image generation requests and interfacing with AI models +* Database operations for recording generation details and image metadata +* Utilities for uploading generated images to cloud storage + +### API + +The `packages/api` package defines the backend API endpoints using Hono: + +* `src/modules/ai/image/image.router.ts`: implements Hono RPC routes for image generation, handles input validation, applies necessary middleware (authentication, credit management), and invokes the core logic from `@turbostarter/ai`. + +### Web + +The Next.js application (`apps/web`) delivers an intuitive user interface: + +* `src/app/[locale]/(apps)/image/**`: contains the Next.js App Router pages and layouts for the image generation experience +* `src/components/image/**`: houses reusable React components tailored to the image generation UI (prompt input, model selector, image gallery, etc.) + +### Mobile + +The Expo/React Native application (`apps/mobile`) provides a native mobile experience: + +* `src/app/image/**`: defines the screens for the mobile image generation interface +* `src/components/image/**`: contains React Native components optimized for mobile interaction +* **API integration**: utilizes the same Hono RPC client (`packages/api`) as the web app for consistent backend communication + +This architecture ensures perfect consistency across platforms while enabling tailored UI implementations optimized for each environment. + + +--- +url: /ai/docs/pdf +title: Chat with PDF +description: Engage in conversations with your PDF documents using AI to extract insights and answer questions. +--- + +The [Chat with PDF](https://ai.turbostarter.dev/pdf) demo application enables intelligent interaction with document content through a conversational AI interface. Upload PDF files and instantly engage in natural dialogue about their contents, asking questions, requesting summaries, and extracting key information with remarkable accuracy. + + + +## Features + +Transform how you interact with document content through these powerful capabilities: + + + + Easily upload PDF files directly into the application for analysis. + + + + Chat with an AI that understands the content of your uploaded PDF, providing + relevant answers based on the text. + + + + Quickly find specific information, key points, or summaries within the + document through natural language queries. + + + + Visualize exactly which document sections informed the AI's responses with + precise source highlighting. + + + + Conduct sophisticated conversations spanning multiple uploaded documents, + enabling cross-document analysis and comparison. + + + +## Setup + +To implement the "Chat with PDF" application in your project, configure these essential backend services: + + + + Set up PostgreSQL with the `pgvector` extension to efficiently store + conversation history, document metadata, and vector embeddings for semantic + search. + + + + Configure S3-compatible cloud storage for secure management of uploaded PDF + documents. + + + +You'll also need to obtain API keys for both the conversational AI models and the embedding models used for text processing. + +## AI models + +This application leverages two complementary AI model types working together: + +1. **Large Language Models (LLMs):** Provide sophisticated natural language understanding to interpret your questions and generate contextually appropriate responses based on document content. +2. **Embedding Models:** Convert document text segments into numerical vector representations that enable efficient semantic similarity search and [Retrieval-Augmented Generation (RAG)](https://en.wikipedia.org/wiki/Retrieval-augmented_generation). + +Configure the providers for the models you wish to use: + + + } /> + + } /> + + } /> + + } /> + + +For comprehensive configuration details, consult the [AI SDK documentation](https://sdk.vercel.ai/docs) covering provider setup and model selection. + +## Data persistence + +The application stores data related to chats, documents, and embeddings to provide a persistent experience. + + + +Application data is organized within a dedicated PostgreSQL schema named `pdf`: + +* `chats`: captures essential metadata for each document-specific conversation session. +* `messages`: stores all user queries and AI responses within conversation threads. +* `documents`: maintains comprehensive tracking of uploaded PDF files, including filenames and storage locations. +* `embeddings`: contains text segments extracted from PDFs along with their vector representations (using [`pgvector`](https://github.com/pgvector/pgvector)'s `vector` data type). To optimize similarity searches critical for RAG processing, the system creates an index (`embeddingIndex` using [HNSW](https://github.com/pgvector/pgvector#hnsw)) on the `embedding` column. + + + +The PDF files uploaded by users are securely stored in your configured [cloud storage](/ai/docs/storage) bucket. The `path` field in the `documents` table maintains the precise reference to each file's location. + +## Structure + +The "Chat with PDF" feature is architected across the monorepo for optimal organization and code reuse: + +### Core + +The `@turbostarter/ai` package (`packages/ai`) contains the essential logic under `modules/pdf`: + +* Comprehensive types, validation schemas, and constants specific to PDF processing +* Advanced document parsing, text segmentation, and embedding generation utilities +* Core API logic for managing conversations, performing RAG-based lookups, and interacting with LLMs +* Database operations for storing and retrieving conversations, documents, and embeddings +* Shared utilities for managing PDF file uploads and downloads + +### API + +The `packages/api` package defines the backend API endpoints using [Hono](https://hono.dev/): + +* `src/modules/ai/pdf/pdf.router.ts`: implements Hono RPC routes for document upload and conversation management, handles input validation, applies middleware (authentication, credit management), and invokes the core functionality from `@turbostarter/ai`. + +### Web + +The [Next.js](https://nextjs.org/) application (`apps/web`) delivers an intuitive user interface: + +* `src/app/[locale]/(apps)/pdf/**`: contains the Next.js App Router pages and layouts for the document conversation experience +* `src/components/pdf/**`: houses reusable React components specific to the PDF interaction UI (document upload, conversation interface, message display) + +### Mobile + +The [Expo](https://expo.dev/)/[React Native](https://reactnative.dev/) application (`apps/mobile`) provides a native mobile experience: + +* `src/app/pdf/**`: defines the screens for the mobile document conversation interface +* `src/components/pdf/**`: contains React Native components optimized for mobile document interaction +* **API integration**: utilizes the same Hono RPC client (`packages/api`) as the web app for consistent backend communication + +This architecture ensures that core AI processing and data handling logic is shared across platforms, while enabling optimized UI implementations tailored to each environment. + + +--- +url: /ai/docs/tts +title: Text to Speech +description: Convert text into natural-sounding speech using advanced AI voice synthesis models. +--- + +The [Text to Speech (TTS)](https://ai.turbostarter.dev/tts) demo application transforms written text into high-quality spoken audio. It leverages state-of-the-art AI models to generate lifelike voices in various languages and styles. + + + +## Features + +Discover the powerful capabilities of this AI-powered voice synthesis solution: + + + + Access a wide range of voices from providers like [Eleven + Labs](https://elevenlabs.io/), including different accents, ages, and + emotional tones, to find the perfect match for your content. + + + + Experience near-instantaneous audio generation with streaming delivery, + providing immediate feedback as your content comes to life. + + + + Enjoy a full-featured playback interface with precise controls for playback + speed and convenient options to download generated audio files. + + + + Fine-tune your audio output with adjustable parameters for pitch, speed, and + pauses, creating the most natural and engaging delivery possible (available + options vary by provider). + + + + Benefit from a thoughtfully designed interface that makes transforming text + to speech effortless and efficient, even for first-time users. + + + +## AI models + +This application primarily utilizes specialized text-to-speech models from [Eleven Labs](https://elevenlabs.io/). + + + } /> + + +For comprehensive information about available voices and advanced customization techniques, consult the [ElevenLabs SDK documentation](https://elevenlabs.io/docs/overview). + +## Structure + +The Text-to-Speech feature is organized across the monorepo for maximum flexibility and maintainability: + +### Core + +The `@turbostarter/ai` package (`packages/ai`) contains the essential logic under `modules/tts`: + +* Comprehensive types, validation schemas, and constants specific to TTS functionality +* Core API logic for processing text-to-speech requests and interfacing with AI models +* Robust handling of generated audio file uploads to cloud storage + +### API + +The `packages/api` package defines the backend API endpoints using [Hono](https://hono.dev/): + +* `src/modules/ai/tts/tts.router.ts`: implements Hono RPC routes for TTS generation, handles input validation, applies critical middleware (authentication, credit management), and invokes the core functionality from `@turbostarter/ai`. + +### Web + +The [Next.js](https://nextjs.org/) application (`apps/web`) provides the user interface: + +* `src/app/[locale]/(apps)/tts/**`: contains the Next.js App Router pages and layouts for the TTS experience +* `src/components/tts/**`: houses reusable React components specific to the TTS interface (text input area, voice selector, audio player, etc.) + +### Mobile + +The [Expo](https://expo.dev/)/[React Native](https://reactnative.dev/) application (`apps/mobile`) provides the native mobile experience: + +* `src/app/tts/**`: defines the screens for the mobile TTS interface +* `src/components/tts/**`: contains React Native components optimized for the mobile experience +* **API interaction**: utilizes the same Hono RPC client (`packages/api`) as the web app for consistent communication with the backend + +This architecture ensures perfect consistency between platforms while allowing for optimized UI implementations tailored to each environment. + + +--- +url: /ai/docs/anthropic +title: Anthropic +description: Setup Anthropic provider and learn how to use it in the starter kit. +--- + +The [Anthropic](https://www.anthropic.com) provider integrates Anthropic's powerful Claude models into your application through the AI SDK, with an emphasis on safety, helpfulness, and natural interactions. + +![Anthropic](/images/docs/ai/providers/anthropic.png) + +## Setup + + + + ### Generate API Key + + Visit the [Anthropic Console](https://console.anthropic.com/) to create an account and generate a new API key for your project. + + + + ### Add API Key to Environment + + Add your generated API key to your project's `.env` file (e.g., in `apps/web`): + + ```bash title=".env" + ANTHROPIC_API_KEY=your-api-key + ``` + + + + ### Configure Provider (Optional) + + The starter kit automatically uses the `ANTHROPIC_API_KEY` environment variable. For advanced configurations (such as proxies or custom headers), refer to the [AI SDK Anthropic documentation](https://sdk.vercel.ai/providers/ai-sdk-providers/anthropic#provider-instance). + + + +## Features + + + + Leverage Anthropic's state-of-the-art Claude models for sophisticated + conversational AI, creative text generation, in-depth analysis, and more + through the intuitive Messages API. + + + + Enable models to understand and process image inputs alongside text for + multimodal applications. + + + + Allow models to interact with external tools and APIs to perform actions and + retrieve real-time information. + + + + Create structured data outputs (like JSON) from natural language prompts, + streamlining the integration of AI capabilities with your existing systems. + + + + Access detailed insights into the model's thought process, enhancing + transparency, debuggability, and trust in AI-generated responses. + + + + (Experimental) Allow models to directly interact with computer desktop + environments to complete complex, multi-step tasks autonomously. + + + +## Use Cases + + + + Craft intelligent, context-aware chatbots capable of nuanced conversations + and sophisticated task completion. Experience this capability in our [Chat + Demo](/ai/docs/chat). + + + + Generate high-quality text for various purposes, or summarize long documents + and conversations accurately. + + + + Extract structured information from unstructured text or analyze complex + data sets combined with visual inputs for comprehensive insights. + + + + Seamlessly integrate Claude models with your existing tools via function + calling to automate complex business processes and tasks. Explore + [Agents](/ai/docs/agents) for advanced implementation options. + + + +## Links + +* [Anthropic Website](https://www.anthropic.com) +* [Anthropic Documentation](https://docs.anthropic.com) +* [AI SDK - Anthropic Provider Docs](https://sdk.vercel.ai/providers/ai-sdk-providers/anthropic) + + +--- +url: /ai/docs/deepseek +title: DeepSeek +description: Integrate DeepSeek's powerful AI models into your applications with minimal setup. +--- + +The [DeepSeek](https://www.deepseek.com/) provider delivers access to DeepSeek's advanced AI models through the AI SDK, bringing reasoning capabilities to your applications. + +![DeepSeek](/images/docs/ai/providers/deepseek.webp) + +## Setup + + + + ### Generate API Key + + Visit the [DeepSeek Platform](https://platform.deepseek.com/) and navigate to the API keys section to create your personal secret key. + + + + ### Add API Key to Environment + + Add your generated API key to your project's `.env` file (e.g., in `apps/web`): + + ```bash title=".env" + DEEPSEEK_API_KEY=your-api-key + ``` + + + + ### Configure Provider (Optional) + + The starter kit automatically utilizes the `DEEPSEEK_API_KEY` environment variable. For advanced configurations, consult the comprehensive [AI SDK DeepSeek documentation](https://sdk.vercel.ai/providers/ai-sdk-providers/deepseek#provider-instance). + + + +## Features + + + + Utilize DeepSeek's language models, known for their deep reasoning + capabilities, for tasks like text generation, translation, and + conversational AI applications. + + + + Tap into models with reasoning abilities designed specifically for complex + problem-solving, logical deduction, and analytical tasks that require deep + understanding. + + + + Enable language models to interact with external tools and functions, + allowing for more complex and automated task execution. + + + +## Use Cases + + + + Create intelligent chatbots that engage in natural, meaningful conversations + and assist users with a wide range of tasks. Experience this capability in + our [Chat Demo](/ai/docs/chat). + + + + Produce diverse, high-quality creative text content including articles, + summaries, code explanations, and marketing copy with language + understanding. + + + + Integrate language models with other tools via function calling to automate + processes like data analysis or report generation. + + + +## Links + +* [DeepSeek Website](https://www.deepseek.com/) +* [DeepSeek Platform](https://platform.deepseek.com/) +* [AI SDK - DeepSeek Provider Docs](https://sdk.vercel.ai/providers/ai-sdk-providers/deepseek) + + +--- +url: /ai/docs/eleven-labs +title: Eleven Labs +description: Setup ElevenLabs and learn how to integrate its AI audio capabilities into the starter kit. +--- + +[ElevenLabs](https://elevenlabs.io/) stands at the forefront of AI audio innovation, specializing in ultra-realistic Text-to-Speech (TTS), voice cloning, and advanced audio generation. While not a native provider within the AI SDK core, ElevenLabs' powerful services integrate seamlessly with AI applications to deliver exceptional voice experiences. + +![ElevenLabs](/images/docs/ai/providers/elevenlabs.jpg) + +## Setup + +Integrating ElevenLabs involves using their purpose-built SDKs (Python, TypeScript/JavaScript) alongside your application logic: + + + + ### Generate API Key + + Visit the [ElevenLabs website](https://elevenlabs.io/), create an account or sign in, then navigate to your profile settings to generate your unique API key. + + + + ### Add API Key to Environment + + Add your API key to your project's `.env` file (e.g., in `apps/web` or the appropriate package): + + ```bash title=".env" + ELEVENLABS_API_KEY=your-api-key + ``` + + + + ### Configure SDK + + Initialize the ElevenLabs client with your API key: + + ```typescript title="client.ts" + import { ElevenLabsClient } from "elevenlabs"; + + import { env } from "../../env"; + + export const client = new ElevenLabsClient({ + apiKey: env.ELEVENLABS_API_KEY, + }); + // Now use the client object... + ``` + + For comprehensive implementation details, refer to the [ElevenLabs Quickstart Guide](https://elevenlabs.io/docs/quickstart). + + + +## Features + +ElevenLabs offers a comprehensive suite of AI audio technologies: + + + + Transform written text into remarkably natural speech across numerous + languages, voices, and styles, with flexible options for quality or + low-latency delivery. + + + + Transcribe spoken audio into text accurately, supporting multiple languages + and providing features like speaker diarization. + + + + Create stunningly accurate digital replicas of voices from audio samples, + with both instant and professional-grade options to suit your needs. + + + + Craft entirely new, unique synthetic voices based on descriptive parameters, + enabling custom voice creation without requiring sample recordings. + + + + Build and deploy end-to-end conversational voice agents, integrating STT, + LLMs (like GPT, Claude, Gemini), TTS, and turn-taking logic. + + + + Automatically dub audio or video content into different languages while + preserving the original voice characteristics. + + + + Create custom sound effects and ambient audio from simple text descriptions, + adding rich audio elements to your applications. + + + + Access an extensive collection of pre-made, ready-to-use voices contributed + by the ElevenLabs community. + + + +## Use Cases + + + + Power conversational AI applications like customer service bots, virtual + assistants, or interactive characters with low-latency TTS. + + + + Create professional-quality narration for audiobooks, articles, videos, and + e-learning content in multiple languages and voices. Experience this in the + [TTS Demo](/ai/docs/tts). + + + + Enhance digital accessibility by converting text content into natural + speech, making your applications more inclusive for users with visual + impairments or reading difficulties. + + + + Deliver dynamic, personalized audio experiences with custom-designed or + cloned voices, creating unique and engaging user interactions. + + + + Utilize dubbing and multilingual TTS to easily adapt content for + international audiences. + + + + Generate character voices, ambient sounds, and dynamic audio for immersive + experiences. + + + +## Links + +* [ElevenLabs Website](https://elevenlabs.io/) +* [ElevenLabs Documentation](https://elevenlabs.io/docs) +* [Developer Quickstart](https://elevenlabs.io/docs/quickstart) +* [API Reference](https://elevenlabs.io/docs/api-reference/introduction) +* [Pricing](https://elevenlabs.io/pricing) + + +--- +url: /ai/docs/google +title: Google AI +description: Setup Google Generative AI provider and learn how to use its models like Gemini in the starter kit. +--- + +The [Google Generative AI](https://ai.google/) provider integrates Google's state-of-the-art models, including the versatile Gemini family, into your applications through the AI SDK. + +![Google Generative AI](/images/docs/ai/providers/google.webp) + +## Setup + + + + ### Generate API Key + + Visit the [Google AI Studio](https://aistudio.google.com/app/apikey) to create your API key. For enterprise applications using Google Cloud, you can alternatively configure authentication via Application Default Credentials or service accounts. + + + + ### Add API Key to Environment + + Add your API key to your project's `.env` file (e.g., in `apps/web`): + + ```bash title=".env" + GOOGLE_GENERATIVE_AI_API_KEY=your-api-key + ``` + + If using Google Cloud credentials instead, ensure they're properly configured in your environment. + + + + ### Configure Provider (Optional) + + The starter kit automatically uses the `GOOGLE_GENERATIVE_AI_API_KEY` environment variable. For advanced configurations (such as proxies, custom API versions, or specific headers), you can create a tailored provider instance using `createGoogleGenerativeAI`. See the [AI SDK Google documentation](https://sdk.vercel.ai/providers/ai-sdk-providers/google-generative-ai#provider-instance) for comprehensive details. + + + +## Features + + + + Leverage Google's advanced Gemini models for chat, text generation, + reasoning, and complex instruction following. + + + + Utilize text embedding models to convert text into numerical representations + for tasks like semantic search, clustering, and RAG. + + + + Analyze and understand various file types (including images and PDFs) + alongside text prompts, enabling rich multimodal applications with + comprehensive content understanding. + + + + Empower models to interact seamlessly with external tools and APIs, allowing + them to perform real-world actions and retrieve up-to-date information for + more capable applications. + + + + Configure safety thresholds to control model responses regarding harmful + content categories. Access safety ratings in the response metadata. + + + + Cache content to optimize context reuse and potentially reduce latency and + costs for repeated queries with similar context. + + + + (With compatible models) Ground responses in real-time search results, + dramatically enhancing factual accuracy and providing up-to-date information + on current topics. + + + +## Use Cases + + + + Create sophisticated conversational agents powered by Gemini models that can + engage in natural dialogue and handle complex, multi-step tasks. Experience + this in our [Chat Demo](/ai/docs/chat). + + + + Generate diverse text formats, from creative writing and marketing copy to + code explanations and summaries. + + + + Build applications that seamlessly analyze and understand images, documents, + and other file types alongside text, creating richer, more contextual user + experiences. + + + + Implement powerful search capabilities or sophisticated Retrieval-Augmented + Generation systems using Google's high-performance embedding models for more + accurate information retrieval. + + + + Streamline operations by connecting language models to external tools and + APIs through function calling, automating complex business processes and + repetitive tasks with minimal human intervention. + + + +## Links + +* [Google AI](https://ai.google/) +* [Google AI Studio](https://aistudio.google.com/) +* [Google Generative AI Documentation](https://ai.google.dev/docs) +* [AI SDK - Google Provider Docs](https://sdk.vercel.ai/providers/ai-sdk-providers/google-generative-ai) + + +--- +url: /ai/docs/meta +title: Meta +description: Setup Meta's Llama models and learn how to use them in the starter kit via various hosting providers. +--- + +The [Meta](https://ai.meta.com/) provider integration brings Meta's cutting-edge Llama family of open-weight models to your applications through the AI SDK. Renowned for their exceptional performance across diverse tasks, these models deliver state-of-the-art capabilities for your AI solutions. + +![Meta Llama](/images/docs/ai/providers/meta.jpg) + +## Setup + +Deploying Llama models in your applications involves leveraging a third-party hosting provider that integrates seamlessly with the AI SDK, such as DeepInfra, Fireworks AI, Amazon Bedrock, Baseten, and others. + + + + ### Choose a hosting provider & get API Key + + Select a trusted provider that hosts Llama models (e.g., [DeepInfra](https://deepinfra.com/), [Fireworks AI](https://fireworks.ai/), or [Amazon Bedrock](https://aws.amazon.com/bedrock/)). Register with your preferred provider and generate a secure API key through their platform console. + + + + ### Add API Key to environment + + Add your provider-specific API key to your project's `.env` file (e.g., in `apps/web`). Use the appropriate environment variable for your chosen provider: + + ```bash title=".env" + # Example for DeepInfra + DEEPINFRA_API_KEY=your-deepinfra-api-key + + # Example for Fireworks AI + FIREWORKS_API_KEY=your-fireworks-api-key + + # Example for Amazon Bedrock (requires AWS credentials) + # AWS_ACCESS_KEY_ID=... + # AWS_SECRET_ACCESS_KEY=... + # AWS_REGION=... + ``` + + + + ### Configure provider + + When implementing AI SDK functions (`generateText`, `streamText`, etc.), initialize the client for your selected provider and specify the appropriate Llama model identifier: + + ```ts + import { generateText } from "ai"; + import { deepinfra } from "@ai-sdk/deepinfra"; + // Or: import { fireworks } from '@ai-sdk/fireworks'; + // Or: import { bedrock } from '@ai-sdk/amazon-bedrock'; + + const { text } = await generateText({ + // Example using DeepInfra + model: deepinfra("meta-llama/Meta-Llama-3.1-8B-Instruct"), + // Example using Fireworks AI + // model: fireworks('accounts/fireworks/models/llama-v3p1-8b-instruct'), + // Example using Amazon Bedrock + // model: bedrock('meta.llama3-1-8b-instruct-v1:0'), + prompt: "Why is the sky blue?", + }); + ``` + + For comprehensive implementation details, consult the AI SDK documentation for your specific provider: [DeepInfra](https://sdk.vercel.ai/providers/ai-sdk-providers/deepinfra), [Fireworks AI](https://sdk.vercel.ai/providers/ai-sdk-providers/fireworks), [Amazon Bedrock](https://sdk.vercel.ai/providers/ai-sdk-providers/amazon-bedrock), etc. + + + +## Features + +Llama models accessible through the AI SDK offer a range of powerful capabilities, with specific features varying based on model version and hosting provider implementation. + + + + Utilize Llama's instruction-tuned models for dialogue generation, + translation, reasoning, and other conversational tasks. Available in various + sizes (e.g., 8B, 70B, 405B). + + + + Empower Llama models to interact with external tools and functions, enabling + complex, multi-step task execution and real-world system integration. + (Capabilities may vary depending on your selected provider). + + + + Leverage Llama's capabilities for complex reasoning problems and generating + code snippets in various programming languages. + + + +## Use Cases + + + + Create intelligent, responsive chatbots capable of natural conversations, + accurate information retrieval, and efficient task execution. Experience + this capability in our [Chat Demo](/ai/docs/chat). + + + + Produce diverse, high-quality text content spanning articles, summaries, + creative narratives, marketing copy, and more—tailored to your specific + requirements. + + + + Boost developer productivity with AI-powered code generation, insightful + code explanations, effective debugging assistance, and programming guidance + across multiple languages. + + + + Streamline operations by combining Llama models with tool usage capabilities + to automate complex business processes and seamlessly interact with your + existing systems. + + + +## Links + +* [Meta AI](https://ai.meta.com/) +* [Meta Llama Models](https://ai.meta.com/llama/) +* [AI SDK - Llama 3.1 Guide](https://sdk.vercel.ai/docs/guides/llama-3_1) +* [AI SDK - Providers](https://sdk.vercel.ai/providers) (Find hosting provider docs here) + + +--- +url: /ai/docs/openai +title: OpenAI +description: Setup OpenAI provider and learn how to use it in the starter kit. +--- + +The [OpenAI](https://openai.com) provider integrates OpenAI's powerful suite of language models, image generation capabilities, and embedding technologies into your application through the AI SDK. + +![OpenAI](/images/docs/ai/providers/openai.png) + +## Setup + + + + ### Generate API Key + + Visit the [OpenAI API keys page](https://platform.openai.com/api-keys) to create your personal secret key for API access. + + + + ### Add API Key to Environment + + Add your API key to your project's `.env` file (e.g., in `apps/web`): + + ```bash title=".env" + OPENAI_API_KEY=your-api-key + ``` + + + + ### Configure Provider (Optional) + + By default, the starter kit automatically uses the `OPENAI_API_KEY` environment variable. For advanced configurations (such as using a proxy or specific organization ID), you can customize the provider instance. For detailed options, refer to the [AI SDK OpenAI documentation](https://sdk.vercel.ai/providers/ai-sdk-providers/openai#provider-instance). + + + +## Features + + + + Leverage state-of-the-art models for building sophisticated conversational + AI, generating creative text formats, and answering complex questions. + + + + Transform text into rich numerical representations with powerful models like + `text-embedding-3-large`, enabling advanced semantic search, intelligent + text clustering, and highly personalized recommendation systems. + + + + Generate unique images from textual descriptions using OpenAI's DALL·E + models, enabling creative applications and content generation. + + + + Convert written text into natural-sounding human speech with various voices + using Text-to-Speech (TTS) models, ideal for accessibility features or voice + interfaces. + + + + Empower models like GPT-4o or GPT-4 Turbo with Vision capabilities to + understand, analyze, and describe the content of images provided in prompts. + + + + Allow language models to intelligently interact with your external tools, + APIs, and custom functions, orchestrating complex multi-step tasks and + creating powerful AI agents that can take actions in the real world. + + + +## Use Cases + + + + Create intelligent, context-aware conversational agents that engage in + natural dialogue, answer complex questions, and complete sophisticated tasks + based on user needs. Experience this capability in our [Chat + Demo](/ai/docs/chat). + + + + Automate the creation of diverse text-based content, including blog posts, + marketing copy, emails, code snippets, and creative writing pieces. + + + + Build advanced search systems that truly understand the meaning behind user + queries, enhanced with Retrieval-Augmented Generation (RAG) for delivering + exceptionally accurate, contextually relevant answers from your data. + + + + Develop applications that can generate images from text prompts or analyze + and interpret the content of existing images for tagging, description, or + moderation. Check out the [Image Generation Demo](/ai/docs/image). + + + + Design engaging voice-enabled experiences, including lifelike virtual + assistants, expressive audiobook narration, real-time translation services, + and accessibility tools that convert text to natural speech for visually + impaired users. + + + + Transform business processes by connecting powerful language models to your + existing tools and systems through function calling, automating complex + workflows for data processing, report generation, customer support, and + more. + + + +## Links + +* [OpenAI Website](https://openai.com/) +* [OpenAI API Documentation](https://platform.openai.com/docs) +* [AI SDK - OpenAI Provider Docs](https://sdk.vercel.ai/providers/ai-sdk-providers/openai) + + +--- +url: /ai/docs/replicate +title: Replicate +description: Setup Replicate provider and learn how to use it in the starter kit. +--- + +The [Replicate](https://replicate.com) provider unlocks access to an extensive library of open-source AI models through a streamlined cloud API, seamlessly integrated with the AI SDK. It's particularly well-known for image generation capabilities. + +![Replicate](/images/docs/ai/providers/replicate.png) + +## Setup + + + + ### Generate API Key + + Visit the [Replicate website](https://replicate.com/), create an account or sign in, then navigate to your account settings to generate your personal API token. + + + + ### Add API Key to Environment + + Add your API token to your project's `.env` file (e.g., in `apps/web`): + + ```bash title=".env" + REPLICATE_API_TOKEN=your-api-key + ``` + + + + ### Configure Provider (Optional) + + The starter kit automatically uses the `REPLICATE_API_TOKEN` environment variable. For advanced configurations (such as proxies or custom headers), you can create a tailored provider instance. For comprehensive details, refer to the [AI SDK Replicate documentation](https://sdk.vercel.ai/providers/ai-sdk-providers/replicate#provider-instance). + + + +## Features + + + + Gain instant access to a diverse ecosystem of community-contributed models + spanning text generation, image creation, audio processing, video synthesis, + and numerous other AI capabilities. + + + + Create stunning visuals using various state-of-the-art open-source models + directly through the AI SDK's intuitive `generateImage` function, with + support for specific model versions and custom parameters. + + + + Fine-tune model behavior by passing specific parameters via + `providerOptions.replicate`, allowing precise control over generation + settings according to each model's unique capabilities. + + + +## Use Cases + + + + Create unique visuals, artwork, or variations based on text prompts using a + diverse set of image models. Check out the [Image Generation + Demo](/ai/docs/image). + + + + Utilize specialized open-source models for specific tasks that might not be + available through other major providers. + + + + Quickly experiment with different community-published models for various AI + tasks without managing infrastructure. + + + +## Links + +* [Replicate Website](https://replicate.com) +* [Replicate Documentation](https://replicate.com/docs) +* [AI SDK - Replicate Provider Docs](https://sdk.vercel.ai/providers/ai-sdk-providers/replicate) + + +--- +url: /ai/docs/xai +title: xAI Grok +description: Setup xAI provider and learn how to use it in the starter kit. +--- + +The [xAI](https://x.ai) provider integrates Grok models into your application using the AI SDK. + +![xAI Grok](/images/docs/ai/providers/xai.webp) + +## Setup + + + + ### Generate API Key + + Visit the [xAI website](https://x.ai) to create an account. After signing in, navigate to your account settings to generate an API key. + + + + ### Add API Key to Environment + + Once you've acquired an API key, add it to your project's `.env` file (e.g., in `apps/web`): + + ```bash title=".env" + XAI_API_KEY=your-api-key + ``` + + + + ### Configure Provider (Optional) + + The starter kit automatically uses the `XAI_API_KEY` environment variable. For advanced configurations and customization options, refer to the comprehensive [AI SDK xAI documentation](https://sdk.vercel.ai/providers/ai-sdk-providers/xai#provider-instance). + + + +## Features + + + + Utilize xAI's language models for conversational AI, text generation, and + other natural language processing tasks. + + + + Enable language models to interact with external tools and functions, + allowing for more complex and automated task execution. + + + + Generate images based on textual descriptions using xAI's models. + + + +## Use Cases + + + + Create intelligent chatbots that engage users in natural, informative + conversations powered by xAI's Grok models, delivering responsive and + contextually relevant interactions. Experience this capability in our [Chat + Demo](/ai/docs/chat). + + + + Produce diverse, high-quality text content across various formats and + styles, harnessing the unique characteristics and capabilities of Grok + models for creative and informational outputs. + + + + Streamline operations by connecting xAI's language models with your existing + tools through function calling, enabling sophisticated automation of complex + business processes and repetitive tasks. + + + + Design striking visuals and artwork directly from text descriptions using + xAI's image generation capabilities, enabling creative applications and rich + visual content. Explore our [Image Generation Demo](/ai/docs/image) to see + these features in action. + + + +## Links + +* [xAI Website](https://x.ai) +* [AI SDK - xAI Provider Docs](https://sdk.vercel.ai/providers/ai-sdk-providers/xai) + + +--- +url: /ai/docs/api +title: API +description: Overview of the API service in TurboStarter AI, including its architecture, technology stack, and core functionalities. +--- + +The API service acts as the central hub for all backend logic within TurboStarter AI. It handles interactions with AI models, data processing, and communication between the frontend and backend systems. + +## Technology + +We use [Hono](https://hono.dev), a lightning-fast web framework optimized for edge computing. This ensures efficient handling of API requests—particularly critical for real-time AI interactions like streaming responses. + +**Importantly, this single API layer serves both web and mobile applications, guaranteeing consistent business logic and data handling across all platforms.** + +## AI integration + +While the API package (`@turbostarter/api`) exposes the endpoints, the core AI logic lives in a dedicated package: `@turbostarter/ai`. This package is strictly responsible for: + +* Communicating with various AI providers and models ([OpenAI](/ai/docs/openai), [Anthropic](/ai/docs/anthropic), [Google AI](/ai/docs/google), etc.) +* Processing and formatting data specifically for AI interactions +* Parsing responses from AI models +* Handling AI-specific data storage or retrieval when necessary + +The `@turbostarter/api` package utilizes `@turbostarter/ai` to perform these AI tasks. The API layer itself focuses on registering Hono routes, applying middlewares (like authentication and validation), and exposing AI functionalities to the frontend applications. + +This separation ensures AI-specific logic remains modular and reusable, while the API package stays focused on request handling and routing. + + + API keys for AI services are managed securely on the backend within these packages, ensuring they never appear client-side. + + +## Middlewares + +Hono middlewares streamline request handling by tackling common tasks before the main logic runs. In TurboStarter AI, they handle: + +* **Authentication:** verifying user sessions to protect routes, ensuring only logged-in users access certain features +* **Validation:** using schemas to check if incoming request data (like query parameters or JSON bodies) matches expected formats, preventing invalid data from reaching route handlers +* **Rate limiting:** shielding the API from abuse by restricting the number of requests a user or IP address can make within a given timeframe +* **Credits management:** automatically checking if a user has enough credits for an AI operation and deducting the cost before proceeding +* **Localization:** detecting the user's preferred language to deliver localized responses and error messages + +These middlewares keep core route logic clean and focused, while consistently enforcing security, usage limits, and data integrity across the API. + +## Core API documentation + +For general information about the API setup, architecture, authentication integration, and how to add new endpoints, please refer to the [Core API documentation](/docs/web/api/overview). + + + +Specific configurations related to AI providers or demo apps can be found in their respective documentation sections. + + +--- +url: /ai/docs/auth +title: Authentication +description: Learn about the authentication flow in TurboStarter AI. +--- + +TurboStarter AI implements a streamlined authentication approach powered by [Better Auth](https://www.better-auth.com/). Since the primary focus is showcasing AI capabilities, we've kept the initial authentication simple, allowing you to quickly integrate and experiment with AI features. + +## Anonymous sessions + +When someone first visits the AI application, an **anonymous session** is automatically created. This establishes a unique user identity without requiring login credentials. + +These anonymous sessions serve two critical purposes: + +1. **Persistence:** links data like chat history or generated content to specific users in your database +2. **Usage control:** enables tracking for rate limiting and the credits system, ensuring fair AI resource usage even for anonymous visitors + +## Extending authentication + +While the default anonymous setup provides a frictionless initial experience, TurboStarter is built for growth. The authentication logic uses Better Auth in the shared `packages/auth` package, ensuring consistency between web and mobile applications. + +When your project needs more sophisticated authentication features like: + +* Email/Password login +* Magic links +* Social logins (OAuth) +* Multi-factor authentication + +You can easily integrate these by leveraging the comprehensive authentication system in the [TurboStarter Core kit](/docs/web). The underlying structure is already in place, making this transition straightforward. + +For detailed implementation guides, check out the core documentation: + + + + + + + +By starting with anonymous sessions, the AI kit lets you focus on building compelling AI features first, while providing a clear path to implement advanced user management and security as your application evolves. + + +--- +url: /ai/docs/billing +title: Billing +description: Discover how to manage billing and payment methods for AI features. +--- + +TurboStarter AI includes a straightforward middleware setup to manage user credits for AI features. This lets you control access based on available credits without complex payment integrations. + +## Credit-based access + +A focused middleware verifies if users have enough credits before allowing them to access specific AI-powered routes or actions. + +```ts title="ai.router.ts" +export const aiRouter = new Hono().post( + "/chat", + rateLimiter, + validate("json", chatMessageSchema), + deductCredits({ + amount: 10, // [!code highlight] + }), + streamChat, +); +``` + +This example shows how the `deductCredits` middleware subtracts a specific amount (10 credits) for each request to the `/chat` endpoint. + +## Coming soon + +We're actively expanding the billing capabilities for AI services, including: + +* **Usage-based billing:** implementing a system where users pay based on their actual consumption of AI resources (tokens used, API calls made, etc.) +* **Payment provider integration:** connecting with popular services like [Stripe](/docs/web/billing/stripe), [Lemon Squeezy](/docs/web/billing/lemon-squeezy), and more for hassle-free payment processing + +## Extending billing + +For more advanced billing scenarios or immediate needs, you can tap into the core TurboStarter billing features. The main documentation provides detailed guidance on setting up and managing billing with third-party providers. + + + +Stay tuned for updates as we enhance the AI-specific billing functionalities! + + +--- +url: /ai/docs/database +title: Database +description: Overview of the database service in TurboStarter AI. +--- + +The database service, managed within the `packages/db` directory (as `@turbostarter/db`), stores data essential for both core application functions and AI features. It ensures that information like user profiles, conversation history, and AI-generated content is reliably preserved and efficiently accessed. + +## Technology + +We've chosen [PostgreSQL](https://www.postgresql.org) as our primary relational database for its exceptional reliability, extensibility (including powerful tools like `pgvector` for similarity searches), and proven track record in production environments. + +Database interactions are handled through [Drizzle ORM](https://orm.drizzle.team/), a cutting-edge TypeScript ORM that offers outstanding type safety (generating types directly from your schema), high performance, and a developer-friendly API. + +For detailed guidance on setup, configuration, schema management (including migrations), and general usage patterns of Drizzle and PostgreSQL in the TurboStarter ecosystem, check out our core documentation: + + + + + + + + + + + +## What is stored in the database? + +Beyond standard application data (like users and accounts), the database plays a crucial role in storing AI-specific information: + +* **Chat history**: saves conversations between users and AI models (including reasoning and usage details), enabling continuous conversations and history features +* **Vector embeddings**: stores numerical representations (vectors) of text data (like document chunks) that power Retrieval-Augmented Generation (RAG) techniques, allowing features like [Chat with PDF](/ai/docs/pdf) to quickly find relevant context from large document collections +* **Document references**: tracks metadata and storage identifiers (paths in [Blob Storage](/ai/docs/storage)) for files like uploaded PDFs or AI-generated images, connecting them to relevant user interactions +* **Tool calls & results**: records actions (such as [web searches](/ai/docs/chat) or calculations) that AI models ([Agents](/ai/docs/agents)) perform, along with their outcomes—valuable for debugging, auditing, and improving agent capabilities + +## Schema + +The core database schema, defined in `packages/db/src/schema`, contains essential tables for the overall application (users, accounts, sessions, etc.). + +To maintain clarity as AI features grow, tables specifically related to AI demo applications (like chat history for the [PDF app](/ai/docs/pdf)) are often placed in dedicated [PostgreSQL schemas](https://www.postgresql.org/docs/current/ddl-schemas.html) (e.g. a schema named `pdf`). + +This logical separation helps manage complexity and isolates feature-specific data structures. You'll typically find AI-specific schema definitions either alongside the relevant demo app code or within the main `packages/db/src/schema` directory, clearly labeled and organized. + + +--- +url: /ai/docs/internationalization +title: Internationalization +description: Learn how we manage internationalization in TurboStarter AI. +--- + +TurboStarter AI builds on the core internationalization (i18n) setup from the main TurboStarter framework. The shared `@turbostarter/i18n` package in `packages/i18n` handles translation management across platforms. + +This gives you the benefit of a proven system using [i18next](https://www.i18next.com/) for managing translations on both web and mobile apps. Plus, the AI models and LLMs integrated within TurboStarter AI generally support multiple languages, enabling interactions beyond what's covered by UI translations alone. + +For detailed information on configuring languages, adding translations, or using the `useTranslation` hook, check out the core documentation: + + + + + + + +## AI-specific translations + +While most translations are shared across the platform, TurboStarter AI introduces a dedicated `ai` namespace within translation files. This namespace contains strings specifically for AI features, demo applications, and UI elements unique to the AI starter kit. + +```json title="packages/i18n/locales/en/ai.json" +{ + "chat": { + "title": "AI Chatbot", + "description": "Engage in intelligent conversations." + }, + "image": { + "title": "Image Generation", + "description": "Create stunning visuals with AI." + } + // ... other AI-specific translations +} +``` + +When adding translations for new AI features or modifying existing ones, place them within the `ai` namespace in the appropriate language files (e.g., `en/ai.json`, `es/ai.json`). This keeps AI-related text organized and separate from core application translations. + + +--- +url: /ai/docs/security +title: Security +description: Learn about the security measures implemented in TurboStarter AI. +--- + + + Remember to regularly review your security implementations and update them as needed. + + +The starter kit incorporates several security measures to protect your application and users when interacting with AI services. + +## Authenticated endpoints + +All AI operation endpoints require user authentication. This is enforced through middleware that verifies the user's session before granting access to any AI features. + + + +The system creates anonymous sessions by default, but you can implement stronger authentication using the core framework's capabilities or the dedicated [authentication setup](/docs/web/auth/overview). + +## Credit-based access + +To prevent AI resource abuse, TurboStarter AI includes a credit-based system. Users receive a limited number of credits that are consumed when using AI features. + + + +This approach avoids misuse while enabling potential monetization. Learn about the implementation details in the [Core billing documentation](/docs/web/billing/overview). + +## Rate limiting + +API endpoints are guarded by rate limiting to prevent abuse and ensure fair usage. This protects your application from potential denial-of-service attacks and excessive request volumes. + + + +We use [`hono-rate-limiter`](https://github.com/rhinobase/hono-rate-limiter), which supports various storage options including [Redis](https://redis.io/), [Cloudflare KV](https://developers.cloudflare.com/workers/runtime-apis/kv/), and [Memcached](https://memcached.org/) for distributed rate limiting. + +## Secure API key handling + +Sensitive API keys for AI providers ([OpenAI](/ai/docs/openai), [Anthropic](/ai/docs/anthropic), [Google AI](/ai/docs/google), etc.) are managed exclusively on the backend. + +They are **NEVER** exposed to client-side code, dramatically reducing the risk of key leakage or unauthorized usage. + +## AI service abuse protection + +While TurboStarter AI provides application-level safeguards like credit limits and rate limiting, it's essential to implement additional protection directly with your AI providers. + + + Always configure spending limits, usage quotas, and monitoring alerts in your + AI provider dashboards (e.g., [OpenAI](/ai/docs/openai), + [Anthropic](/ai/docs/anthropic), [Google AI](/ai/docs/google)). These serve as + critical safety nets against unexpected costs or potential abuse that might + bypass your application-level controls. + + +By combining application-level security with provider-level controls, you'll build truly robust and secure AI applications. + + +--- +url: /ai/docs/storage +title: Storage +description: Explore cloud storage services for AI applications. +--- + +Blob storage in TurboStarter AI offers a scalable solution for handling the diverse file types essential to modern AI applications. It works seamlessly with S3-compatible services including [AWS S3](https://aws.amazon.com/s3/), [Cloudflare R2](https://www.cloudflare.com/products/r2/), and [MinIO](https://min.io/). + +## Use cases + +Blob storage powers several key AI functions: + +* **Managing user uploads:** safely storing files like PDFs or images that users upload for AI processing, as seen in the ["Chat with PDF" demo](/ai/docs/pdf) and image analysis features +* **Preserving AI-generated content:** storing outputs from AI models, such as images from the [Image Generation demo](/ai/docs/image) or audio files from the [Text-to-Speech demo](/ai/docs/tts) +* **Powering RAG systems:** housing documents and files that serve as knowledge sources for Retrieval-Augmented Generation, used in demos like [Chat with PDF](/ai/docs/pdf) and intelligent [Agents](/ai/docs/agents) + +## Security + +Properly configuring bucket permissions for your storage provider is critical. Always restrict access based on the principle of least privilege: + +* Buckets containing user uploads or sensitive RAG documents should typically **not** be publicly accessible +* Set precise permissions that allow your application server (API) to read/write as needed while blocking unauthorized access + +Refer to your provider's documentation ([AWS S3](https://docs.aws.amazon.com/AmazonS3/latest/userguide/security-best-practices.html), [Cloudflare R2](https://developers.cloudflare.com/security-center/security-insights/roles-and-permissions/), [MinIO](https://min.io/docs/minio/linux/administration/identity-access-management/policy-based-access-control.html)) for specific guidance on securing your storage buckets. + +## Storage documentation + +For detailed setup instructions, configuration options for different storage providers, and implementation best practices, check out the core storage documentation: + + + +In summary, blob storage is essential for building sophisticated AI applications - enabling you to handle user uploads, store AI-generated files, and manage RAG document collections. + + +--- +url: /ai/docs/ui +title: UI +description: Learn more about UI components and design system in AI starter kit. +--- + +TurboStarter AI builds on the core TurboStarter UI foundation to create engaging interfaces for all AI features. + +The UI architecture uses shared components and styles with platform-specific implementations: + +* **`@turbostarter/ui`**: includes shared assets, themes, and fundamental styles +* **`@turbostarter/ui-web`**: contains web components built with [Tailwind CSS](https://tailwindcss.com), [Radix UI](https://www.radix-ui.com/), and [shadcn/ui](https://ui.shadcn.com) +* **`@turbostarter/ui-mobile`**: delivers mobile components using [Uniwind](https://uniwind.dev/) and [react-native-reusables](https://reactnativereusables.com/) + +This approach maximizes code reuse while optimizing for each platform's unique capabilities. + +## UI in AI applications + +The AI starter kit leverages this foundation to create intuitive interfaces for various features and demo apps: + + + + Components for displaying conversations, user input, and streaming responses + (used in [Chatbot](/ai/docs/chat) and [Chat with PDF](/ai/docs/pdf) demos). + + + + Displaying AI-generated images as masonry grids with options for interaction + (used in [Image Generation](/ai/docs/image) demo). + + + + Structured forms for configuring AI tasks (e.g., selecting models, adjusting + parameters, modifying prompts). + + + + Visual feedback during AI processing, such as loading spinners or progress + indicators (e.g. [Text to Speech](/ai/docs/tts) voice avatar animation). + + + + UI elements for users to rate or provide feedback on AI outputs. This can + include thumbs up/down buttons or text input fields for comments. + + + + Components for displaying error messages or alerts when AI tasks fail or + encounter issues. + + + + Ensuring that all UI components are usable for individuals with + disabilities, including keyboard navigation and screen reader support. + + + + Components for displaying data or model outputs visually, such as charts, + graphs, or progress bars. + + + +## Generative UI + +A standout aspect of AI applications is their ability to dynamically create or modify UI elements based on AI responses. TurboStarter AI enables this through: + +* **AI SDK components**: libraries like the [AI SDK](https://sdk.vercel.ai/docs/introduction) provide specialized components and hooks (like `useActions` and `useUIState`) designed to render UI based on AI actions or structured data. This creates interactive elements—buttons, forms, or visualizations—that appear dynamically within conversations or workflows. +* **Structured output**: AI models can return data in specific formats (such as JSON) that your frontend parses to render appropriate components, display information, or trigger actions. For example, an AI might return product details that automatically render as interactive cards. +* **Conditional rendering**: the platform uses standard React patterns for showing, hiding, or transforming UI components based on AI interaction states. This creates smooth transitions between loading states, results displays, and follow-up options tailored to AI suggestions. + +This approach delivers truly responsive user experiences where interfaces adapt intelligently to ongoing AI processes. The [Chat demo app](/ai/docs/chat) showcases these generative UI capabilities in action. + +## Customization and further details + +Customizing appearance (themes, styling) or adding new UI components follows the same process as core TurboStarter applications. For complete guides on styling, theme management, and component development, see our core documentation: + + + + + + + +By leveraging the core UI system, TurboStarter AI ensures consistent user experiences across platforms while letting you focus on creating unique AI functionalities. + + +--- +url: /ai/docs/architecture +title: Architecture +description: A quick overview of the different parts of the TurboStarter AI. +--- + +TurboStarter AI integrates several best-in-class open source libraries to power its diverse functionalities, including authentication, data persistence, text generation, and more. Here's a concise overview of the architecture that makes everything work together. + + + +## Application framework + +The project leverages a [monorepo structure](https://turbo.build/repo) powered by [Turborepo](https://turbo.build/) to enable efficient code sharing and consistent tooling across the entire application ecosystem. This approach creates a single source of truth for shared code and dramatically simplifies dependency management. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +### Web + +Built with [Next.js](https://nextjs.org) and [React](https://react.dev), the web application leverages server-side rendering and static site generation for optimal performance and SEO. The UI is styled with [Tailwind CSS](https://tailwindcss.com) and [shadcn/ui](https://ui.shadcn.com) components for rapid development and consistent design. API routes are handled by [Hono](https://hono.dev) for edge computing, chosen for its minimal overhead and excellent TypeScript support. + + + +### Mobile + +The mobile application uses [React Native](https://reactnative.dev) with [Expo](https://expo.dev) for cross-platform development. This combination was selected for its ability to share up to 90% of code between platforms while maintaining native performance. The integration with the monorepo allows seamless sharing of business logic and types with the web application. + + + +## API + +The API is implemented as a dedicated package using [Hono](https://hono.dev), a lightweight framework optimized for edge computing. This architectural decision creates a clear separation between frontend and backend logic, enhancing maintainability and testability. + +Hono's exceptional TypeScript support ensures type safety across all endpoints, while its minimal footprint and edge-first design deliver outstanding performance. + + + +## Model providers + +TurboStarter AI seamlessly integrates with leading AI model providers including [OpenAI](/ai/docs/openai), [Anthropic](/ai/docs/anthropic), [Google AI](/ai/docs/google), [xAI](/ai/docs/xai), and more. The architecture employs [AI SDK](https://sdk.vercel.ai/) to create a unified interface across diverse providers, simplifying experimentation with different models. + +The platform strategically utilizes specialized models for distinct AI tasks: + +* **Text generation** models for conversational AI and content creation +* **Structured output** models for precise data extraction and formatting +* **Image generation** models for visual content creation +* **Voice synthesis** models for natural audio production +* **Embedding** models for semantic search and information retrieval + +Switching models requires just a **one-line code change**, allowing you to rapidly adapt to emerging models or change providers based on your specific requirements. This flexibility ensures your application can leverage the latest AI advancements without extensive refactoring. + +## Authentication + +The applications use [Better Auth](https://www.better-auth.com/) for authentication, providing a secure and flexible authentication system. By default, the AI implementation creates an anonymous user session at startup, which is then used for all subsequent queries and interactions with the AI models. This approach maintains user context across sessions while minimizing friction. + +For more sophisticated authentication requirements, you can easily extend the flow by leveraging the [Core implementation](/docs/web/auth/overview), which supports email/password authentication, magic links, OAuth providers, and more. This modular design lets you implement precisely the level of security your application demands. + + + +## Persistence + +Persistence in TurboStarter AI refers to the system's ability to store and retrieve data from a database. The application uses [PostgreSQL](https://www.postgresql.org/) as its primary database to store critical information such as: + +* Chat history and conversation context +* User accounts and preference settings +* Vector embeddings for retrieval-augmented generation + +To interact with the database from route handlers and server actions, TurboStarter AI leverages [Drizzle ORM](https://orm.drizzle.team/), a high-performance TypeScript ORM that provides type-safe database operations. This ensures robust data integrity and simplified query construction throughout the application. + +A key advantage of Drizzle is its compatibility with multiple database providers including [Neon](https://neon.tech/), [Supabase](https://supabase.com/), and [PlanetScale](https://planetscale.com/). This flexibility allows seamless switching between providers based on your specific requirements without modifying queries or schema definitions — making your application highly adaptable to evolving infrastructure needs. + + + +## Blob storage + +File storage is managed through S3-compatible services, providing scalable, reliable storage for diverse file types. The system efficiently handles user-uploaded images, AI-generated content, and document files. This approach ensures optimal file management and straightforward integration with various storage providers including [AWS S3](https://aws.amazon.com/s3/), [Cloudflare R2](https://www.cloudflare.com/products/r2/), or [MinIO](https://min.io/). + + + +## Security + +Security is implemented comprehensively to protect both the application and its users. All API endpoints incorporate **rate limiting** to prevent abuse and ensure fair resource allocation. + +The system uses a **credits-based access** control system, where each user has a limited number of credits for AI operations, preventing resource exhaustion and enabling monetization options. + +All external API interactions, including those with AI model providers, occur exclusively server-side. This ensures that sensitive API keys are **never exposed** to client-side code, significantly reducing vulnerability to unauthorized access or credential theft. + +Additionally, the system implements industry-standard security practices including thorough input validation, proper authentication enforcement, and regular dependency security audits. + + + + +--- +url: /ai/docs +title: Get started +description: An overview of the TurboStarter AI starter kit. +--- + +TurboStarter AI is a **starter kit with ready-to-use demo apps** that helps you quickly build powerful AI applications without starting from scratch. Whether you're launching a small side project or a full-scale enterprise solution, it provides the structure you need to jump right into building your own unique AI application. + + + +## Features + +TurboStarter AI comes packed with features designed to accelerate your development process: + +### Core framework + + + + + + + + + + + +### AI + + + + + + + + + + + + + +### Data storage + + + + + + + + + + + +### Authentication + + + + + + + + + + + +### User interface + + + + + + + + + +## Demo apps + +TurboStarter AI includes several production-ready demo applications that showcase diverse AI capabilities. Use these examples to understand implementation patterns and jumpstart your own projects. + + + } /> + + } /> + + } /> + + } /> + + } /> + + +## Scope of this documentation + +This documentation focuses specifically on the AI features, architecture, and demo applications included in the **TurboStarter AI** kit. While we provide comprehensive coverage of AI integrations, for information about core framework elements (authentication, billing, etc.), please refer to the [Core documentation](/docs/web). + +Our goal is to guide you through setting up, customizing, and deploying the AI starter kit efficiently. Where relevant, we include links to official documentation for the integrated AI providers and libraries. + +## Setup + +Getting started with TurboStarter AI requires configuring the core applications first. For detailed setup instructions, refer to: + + + } /> + + } /> + + +After establishing the core applications, you can configure specific AI providers and demo applications using the dedicated sections in this documentation (see sidebar). For a quick start, you might also want to check our [TurboStarter CLI guide](/blog/the-only-turbo-cli-you-need-to-start-your-next-project-in-seconds) to bootstrap your project in seconds. + + + When working with the AI starter kit, remember to use the `ai` repository instead of `core` for Git commands. For example, use `git clone turbostarter/ai` rather than `git clone turbostarter/core`. + + +## Deployment + +Deploying TurboStarter AI follows the same process as deploying the core web application. Ensure you configure all necessary environment variables, including those for your selected AI providers (like [OpenAI](/ai/docs/openai), [Anthropic](/ai/docs/anthropic), etc.), in your deployment environment. + +For comprehensive deployment instructions across various platforms, consult our core deployment guides: + + + + + + + + + + + + + +For mobile app store deployment, refer to our mobile publishing guides: + + + + + + + +Each AI demo app may have specific deployment considerations, so check their dedicated documentation sections for additional guidance. + +## `llms.txt` + +Access the complete TurboStarter documentation in Markdown format at [/llms.txt](/llms.txt). This file contains all documentation in an LLM-friendly format, enabling you to ask questions about TurboStarter using the most current information. + +### Example usage + +To query an LLM about TurboStarter: + +1. Copy the documentation contents from [/llms.txt](/llms.txt) +2. Use this prompt format with your preferred LLM: + +``` +Documentation: +{paste documentation here} + +--- + +Based on the above documentation, answer the following: +{your question} +``` + +## Let's build amazing AI! + +We're excited to help you create innovative AI-powered applications quickly and efficiently. If you have questions, encounter issues, or want to showcase your creations, connect with our community: + +* [Follow updates on X](https://x.com/turbostarter_) +* [Join our Discord](https://discord.gg/KjpK2uk3JP) +* [Report issues on GitHub](https://github.com/turbostarter) +* [Contact us via email](mailto:hello@turbostarter.dev) + +Happy building! 🚀 + + +--- +url: /ai/docs/stack +title: Tech stack +description: Learn which tools and libraries power TurboStarter AI. +--- + +## Turborepo + +[Turborepo](https://turbo.build/) is a high-performance monorepo tool that optimizes dependency management and script execution across your project. We chose this monorepo setup to simplify feature management and enable seamless code sharing between packages. + +} /> + +## Next.js + +[Next.js](https://nextjs.org) is a powerful [React](https://react.dev) framework that delivers server-side rendering, static site generation, and more. We selected Next.js for its exceptional flexibility and developer experience. It also serves as the foundation for our serverless API. + + + } /> + + } /> + + +## React Native + Expo + +[React Native](https://reactnative.dev/) is a leading open-source framework created by Facebook that enables building native mobile applications using [React](https://react.dev). It provides access to native platform capabilities while maintaining the development efficiency of React. + +[Expo](https://expo.dev/) extends React Native with a comprehensive toolkit that streamlines development, building, and deployment of iOS, Android, and web apps from a single codebase. + + + } /> + + } /> + + +## AI SDK + +[Vercel AI SDK](https://sdk.vercel.ai/) provides a robust toolkit for building AI-powered applications. It offers essential utilities and components for integrating advanced AI features, including streaming responses, interactive chat interfaces, and more. + +} /> + +## LangChain + +[LangChain](https://js.langchain.com/) is a sophisticated framework designed for language model-powered applications. It delivers critical abstractions and tools for building complex AI systems, including prompt management, memory systems, and agent architectures. + +} /> + +## Hono + +[Hono](https://hono.dev) is an ultrafast, lightweight web framework optimized for edge computing. It includes a type-safe RPC client for secure function calls from the frontend. We leverage Hono to create efficient serverless API endpoints. + +} /> + +## Tailwind CSS + +[Tailwind CSS](https://tailwindcss.com) is a utility-first CSS framework that accelerates UI development without writing custom CSS. We complement it with [Radix UI](https://radix-ui.com), a collection of accessible headless components, and [shadcn/ui](https://ui.shadcn.com), which lets you generate beautifully designed components with a single command. + + + } /> + + } /> + + } /> + + +## Drizzle + +[Drizzle](https://orm.drizzle.team/) is a type-safe, high-performance [ORM](https://orm.drizzle.team/docs/overview) (Object-Relational Mapping) for modern database management. It generates TypeScript types from your schema and enables fully type-safe queries. + +We use [PostgreSQL](https://www.postgresql.org) as our default database, but Drizzle's flexibility allows you to easily switch to MySQL, SQLite, or any [other supported database](https://orm.drizzle.team/docs/connect-overview) by updating a few configuration lines. + + + } /> + + } /> + diff --git a/.context/turbostarter-framework-context/refresh-docs.py b/.context/turbostarter-framework-context/refresh-docs.py new file mode 100644 index 0000000..db742a6 --- /dev/null +++ b/.context/turbostarter-framework-context/refresh-docs.py @@ -0,0 +1,345 @@ +#!/usr/bin/env python3 +""" +TurboStarter Documentation Chunker + +Downloads llms.txt from TurboStarter and splits it into organized markdown files. +Creates an index.md with navigation and a CLAUDE.md context file. + +Usage: + python refresh-docs.py + +Or make executable: + chmod +x refresh-docs.py + ./refresh-docs.py +""" + +import os +import re +import urllib.request +from pathlib import Path +from datetime import datetime +from collections import defaultdict + +# Configuration +LLMS_URL = "https://www.turbostarter.dev/llms.txt" +DOCS_DIR = Path(__file__).parent +OUTPUT_DIR = DOCS_DIR / "sections" + +def download_llms_txt(): + """Download the latest llms.txt from TurboStarter.""" + print(f"Downloading from {LLMS_URL}...") + with urllib.request.urlopen(LLMS_URL) as response: + content = response.read().decode('utf-8') + + # Save full file + full_path = DOCS_DIR / "llms-full.txt" + full_path.write_text(content) + print(f"Saved full file to {full_path} ({len(content)} bytes)") + return content + +def parse_frontmatter(text): + """Extract YAML frontmatter from a document section.""" + match = re.match(r'^---\s*\n(.*?)\n---\s*\n', text, re.DOTALL) + if not match: + return None, text + + frontmatter = {} + for line in match.group(1).strip().split('\n'): + if ':' in line: + key, value = line.split(':', 1) + frontmatter[key.strip()] = value.strip() + + content = text[match.end():] + return frontmatter, content + +def url_to_path(url): + """Convert URL path to filesystem path.""" + # /docs/web/database -> web/database + path = url.lstrip('/') + if path.startswith('docs/'): + path = path[5:] + return path + +def chunk_docs(content): + """Split llms.txt into individual documents.""" + # Split by document separator (--- at start of line followed by url:) + sections = re.split(r'\n(?=---\s*\nurl:)', content) + + docs = [] + for section in sections: + section = section.strip() + if not section: + continue + + frontmatter, body = parse_frontmatter(section) + if frontmatter and 'url' in frontmatter: + docs.append({ + 'url': frontmatter.get('url', ''), + 'title': frontmatter.get('title', 'Untitled'), + 'description': frontmatter.get('description', ''), + 'content': body.strip(), + 'path': url_to_path(frontmatter.get('url', '')) + }) + + return docs + +def organize_by_category(docs): + """Group documents by their top-level category.""" + categories = defaultdict(list) + for doc in docs: + parts = doc['path'].split('/') + if parts: + category = parts[0] # web, mobile, extension, etc. + categories[category].append(doc) + return dict(categories) + +def save_docs(docs): + """Save chunked documents to filesystem.""" + # Clean output directory + if OUTPUT_DIR.exists(): + import shutil + shutil.rmtree(OUTPUT_DIR) + OUTPUT_DIR.mkdir(parents=True) + + # Group by category + categories = organize_by_category(docs) + + saved_files = [] + for category, category_docs in categories.items(): + category_dir = OUTPUT_DIR / category + category_dir.mkdir(parents=True, exist_ok=True) + + for doc in category_docs: + # Create subdirectories if needed + path_parts = doc['path'].split('/') + if len(path_parts) > 1: + subdir = category_dir / '/'.join(path_parts[1:-1]) if len(path_parts) > 2 else category_dir + subdir.mkdir(parents=True, exist_ok=True) + filename = path_parts[-1] + '.md' + filepath = subdir / filename + else: + filepath = category_dir / 'index.md' + + # Build markdown content + md_content = f"""--- +title: {doc['title']} +description: {doc['description']} +url: {doc['url']} +--- + +# {doc['title']} + +{doc['content']} +""" + filepath.write_text(md_content) + saved_files.append({ + 'filepath': filepath.relative_to(DOCS_DIR), + 'title': doc['title'], + 'description': doc['description'], + 'url': doc['url'], + 'category': category + }) + + print(f"Saved {len(saved_files)} documentation files") + return saved_files, categories + +def generate_index(saved_files, categories, docs): + """Generate index.md with rich contextual navigation.""" + lines = [ + "# TurboStarter Documentation Index", + "", + f"**Last updated:** {datetime.now().strftime('%Y-%m-%d %H:%M')} ", + f"**Total pages:** {len(docs)} ", + f"**Source:** https://www.turbostarter.dev/llms.txt", + "", + "---", + "", + "## Quick Reference", + "", + "Use this index to find TurboStarter documentation. Each link includes a description.", + "", + ] + + # Category overview with counts and key topics + lines.append("### Categories Overview") + lines.append("") + lines.append("| Platform | Pages | Key Topics |") + lines.append("|----------|-------|------------|") + + for category in sorted(categories.keys()): + count = len(categories[category]) + # Extract unique subcategories as key topics + subcats = set() + for doc in categories[category]: + parts = doc['path'].split('/') + if len(parts) > 1: + subcats.add(parts[1]) + topics = ', '.join(sorted(subcats)[:5]) + if len(subcats) > 5: + topics += f' (+{len(subcats)-5} more)' + lines.append(f"| **{category.title()}** | {count} | {topics} |") + + lines.append("") + lines.append("---") + lines.append("") + + # Detailed sections with full context + for category in sorted(categories.keys()): + lines.append(f"## {category.title()}") + lines.append("") + + # Group by subcategory + subcats = defaultdict(list) + for doc in categories[category]: + parts = doc['path'].split('/') + subcat = parts[1] if len(parts) > 1 else 'overview' + subcats[subcat].append(doc) + + for subcat in sorted(subcats.keys()): + subcat_title = subcat.replace('-', ' ').replace('_', ' ').title() + lines.append(f"### {subcat_title}") + lines.append("") + + # Add a contextual summary based on descriptions + subcat_docs = subcats[subcat] + if len(subcat_docs) > 3: + lines.append(f"*{len(subcat_docs)} pages covering {subcat_title.lower()} functionality.*") + lines.append("") + + # Table format for better scanning + lines.append("| Topic | Description |") + lines.append("|-------|-------------|") + for doc in sorted(subcat_docs, key=lambda d: d['title']): + filepath = f"sections/{doc['path']}.md" + # Truncate long descriptions + desc = doc['description'][:80] + '...' if len(doc['description']) > 80 else doc['description'] + lines.append(f"| [{doc['title']}]({filepath}) | {desc} |") + + lines.append("") + + # Quick lookup section + lines.append("---") + lines.append("") + lines.append("## Quick Lookup by Keyword") + lines.append("") + lines.append("Common searches and where to find them:") + lines.append("") + + # Build keyword index from titles and descriptions + keyword_map = defaultdict(list) + keywords_of_interest = [ + 'auth', 'login', 'oauth', 'session', + 'database', 'drizzle', 'postgres', 'migration', + 'api', 'hono', 'endpoint', 'route', + 'billing', 'stripe', 'payment', 'subscription', + 'email', 'smtp', 'template', + 'storage', 's3', 'upload', 'file', + 'i18n', 'translation', 'locale', + 'admin', 'user', 'role', 'permission', + 'organization', 'team', 'member', + 'ai', 'openai', 'anthropic', 'chat', + 'deploy', 'vercel', 'docker', + 'test', 'vitest', 'playwright', + ] + + for doc in docs: + text = f"{doc['title']} {doc['description']}".lower() + for kw in keywords_of_interest: + if kw in text: + keyword_map[kw].append(doc) + + # Output keyword table + lines.append("| Keyword | Related Docs |") + lines.append("|---------|--------------|") + for kw in sorted(keyword_map.keys()): + related = keyword_map[kw][:3] # Max 3 per keyword + links = ', '.join([f"[{d['title']}](sections/{d['path']}.md)" for d in related]) + if len(keyword_map[kw]) > 3: + links += f" (+{len(keyword_map[kw])-3} more)" + lines.append(f"| `{kw}` | {links} |") + + lines.append("") + + index_path = DOCS_DIR / "index.md" + index_path.write_text('\n'.join(lines)) + print(f"Generated index at {index_path}") + +def generate_claude_md(): + """Generate CLAUDE.md context file for the docs folder.""" + content = """# TurboStarter Framework Context + +TurboStarter framework documentation for AI context loading. + +## When to Read More + +**Read `index.md`** if you need to: +- Find TurboStarter documentation on a specific topic +- Search by keyword (auth, database, billing, api, etc.) +- Understand what documentation is available + +**Read `framework.md`** for: +- pnpm commands and workflows +- Monorepo structure +- Code conventions + +## Quick Reference + +| Need | Read | +|------|------| +| Commands & patterns | `framework.md` | +| Authentication | `sections/web/auth/` | +| Database/Drizzle | `sections/web/database/` | +| API/Hono | `sections/web/api/` | +| Billing/Stripe | `sections/web/billing/` | +| UI Components | `sections/web/ui/` | +| Organizations | `sections/web/organizations/` | +| i18n | `sections/web/i18n/` | +| Mobile | `sections/mobile/` | + +## Refreshing + +```bash +python .context/turbostarter-framework-context/refresh-docs.py +``` + +## Notes + +- These docs are **subordinate** to `.context/CLAUDE.md` +- Adapt patterns to match existing codebase, don't copy verbatim +- When in doubt, check the actual code in `packages/` and `apps/` +""" + + claude_path = DOCS_DIR / "CLAUDE.md" + claude_path.write_text(content) + print(f"Generated CLAUDE.md at {claude_path}") + +def main(): + print("=" * 60) + print("TurboStarter Documentation Chunker") + print("=" * 60) + print() + + # Download latest docs + content = download_llms_txt() + + # Parse and chunk + print("Parsing documentation sections...") + docs = chunk_docs(content) + print(f"Found {len(docs)} documentation pages") + + # Save to filesystem + print("Saving chunked files...") + saved_files, categories = save_docs(docs) + + # Generate navigation files + print("Generating navigation files...") + generate_index(saved_files, categories, docs) + generate_claude_md() + + print() + print("=" * 60) + print("Done! Documentation is ready in .context/turbostarter-framework-context/") + print("=" * 60) + +if __name__ == "__main__": + main() diff --git a/.context/turbostarter-framework-context/sections/ai/docs.md b/.context/turbostarter-framework-context/sections/ai/docs.md new file mode 100644 index 0000000..38ef88d --- /dev/null +++ b/.context/turbostarter-framework-context/sections/ai/docs.md @@ -0,0 +1,173 @@ +--- +title: Get started +description: An overview of the TurboStarter AI starter kit. +url: /ai/docs +--- + +# Get started + +TurboStarter AI is a **starter kit with ready-to-use demo apps** that helps you quickly build powerful AI applications without starting from scratch. Whether you're launching a small side project or a full-scale enterprise solution, it provides the structure you need to jump right into building your own unique AI application. + + + +## Features + +TurboStarter AI comes packed with features designed to accelerate your development process: + +### Core framework + + + + + + + + + + + +### AI + + + + + + + + + + + + + +### Data storage + + + + + + + + + + + +### Authentication + + + + + + + + + + + +### User interface + + + + + + + + + +## Demo apps + +TurboStarter AI includes several production-ready demo applications that showcase diverse AI capabilities. Use these examples to understand implementation patterns and jumpstart your own projects. + + + } /> + + } /> + + } /> + + } /> + + } /> + + +## Scope of this documentation + +This documentation focuses specifically on the AI features, architecture, and demo applications included in the **TurboStarter AI** kit. While we provide comprehensive coverage of AI integrations, for information about core framework elements (authentication, billing, etc.), please refer to the [Core documentation](/docs/web). + +Our goal is to guide you through setting up, customizing, and deploying the AI starter kit efficiently. Where relevant, we include links to official documentation for the integrated AI providers and libraries. + +## Setup + +Getting started with TurboStarter AI requires configuring the core applications first. For detailed setup instructions, refer to: + + + } /> + + } /> + + +After establishing the core applications, you can configure specific AI providers and demo applications using the dedicated sections in this documentation (see sidebar). For a quick start, you might also want to check our [TurboStarter CLI guide](/blog/the-only-turbo-cli-you-need-to-start-your-next-project-in-seconds) to bootstrap your project in seconds. + + + When working with the AI starter kit, remember to use the `ai` repository instead of `core` for Git commands. For example, use `git clone turbostarter/ai` rather than `git clone turbostarter/core`. + + +## Deployment + +Deploying TurboStarter AI follows the same process as deploying the core web application. Ensure you configure all necessary environment variables, including those for your selected AI providers (like [OpenAI](/ai/docs/openai), [Anthropic](/ai/docs/anthropic), etc.), in your deployment environment. + +For comprehensive deployment instructions across various platforms, consult our core deployment guides: + + + + + + + + + + + + + +For mobile app store deployment, refer to our mobile publishing guides: + + + + + + + +Each AI demo app may have specific deployment considerations, so check their dedicated documentation sections for additional guidance. + +## `llms.txt` + +Access the complete TurboStarter documentation in Markdown format at [/llms.txt](/llms.txt). This file contains all documentation in an LLM-friendly format, enabling you to ask questions about TurboStarter using the most current information. + +### Example usage + +To query an LLM about TurboStarter: + +1. Copy the documentation contents from [/llms.txt](/llms.txt) +2. Use this prompt format with your preferred LLM: + +``` +Documentation: +{paste documentation here} + +--- + +Based on the above documentation, answer the following: +{your question} +``` + +## Let's build amazing AI! + +We're excited to help you create innovative AI-powered applications quickly and efficiently. If you have questions, encounter issues, or want to showcase your creations, connect with our community: + +* [Follow updates on X](https://x.com/turbostarter_) +* [Join our Discord](https://discord.gg/KjpK2uk3JP) +* [Report issues on GitHub](https://github.com/turbostarter) +* [Contact us via email](mailto:hello@turbostarter.dev) + +Happy building! 🚀 diff --git a/.context/turbostarter-framework-context/sections/ai/docs/agents.md b/.context/turbostarter-framework-context/sections/ai/docs/agents.md new file mode 100644 index 0000000..502cf34 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/ai/docs/agents.md @@ -0,0 +1,56 @@ +--- +title: Agents +description: Build powerful, autonomous AI agents capable of performing complex tasks within your web and mobile applications. +url: /ai/docs/agents +--- + +# Agents + + + This feature is currently under development and will be + available in a future release. + + [See roadmap](https://github.com/orgs/turbostarter/projects/1) + + +The AI Agents demo will showcase how to create intelligent, autonomous agents capable of executing complex tasks within your web and mobile applications. + +These agents will leverage advanced AI techniques to interact with users, tools, and data sources. + +## Features + + + + Design agents once and deploy them seamlessly across multiple platforms + including React, React Native, Expo, and Next.js through a unified + architecture. + + + + Implement sophisticated context retention that allows agents to maintain + state and recall critical information across conversations and devices with + perfect continuity. + + + + Enable agents to take meaningful actions by integrating with external tools, + accessing APIs, and executing functions dynamically within secure, + controlled environments. + + + + Leverage the [Model Context + Protocol](https://modelcontextprotocol.io/introduction) to standardize + context delivery between agents and Large Language Models (LLMs). This + enables frictionless connections to diverse data sources and tools, + dramatically enhancing agent capabilities. + + + + Orchestrate complex workflows combining Retrieval-Augmented Generation + (RAG), tool utilization, and MCP server interactions to solve sophisticated + tasks that previously required human intervention. + + + +Stay tuned for the release of this exciting functionality! diff --git a/.context/turbostarter-framework-context/sections/ai/docs/anthropic.md b/.context/turbostarter-framework-context/sections/ai/docs/anthropic.md new file mode 100644 index 0000000..80db6c8 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/ai/docs/anthropic.md @@ -0,0 +1,104 @@ +--- +title: Anthropic +description: Setup Anthropic provider and learn how to use it in the starter kit. +url: /ai/docs/anthropic +--- + +# Anthropic + +The [Anthropic](https://www.anthropic.com) provider integrates Anthropic's powerful Claude models into your application through the AI SDK, with an emphasis on safety, helpfulness, and natural interactions. + +![Anthropic](/images/docs/ai/providers/anthropic.png) + +## Setup + + + + ### Generate API Key + + Visit the [Anthropic Console](https://console.anthropic.com/) to create an account and generate a new API key for your project. + + + + ### Add API Key to Environment + + Add your generated API key to your project's `.env` file (e.g., in `apps/web`): + + ```bash title=".env" + ANTHROPIC_API_KEY=your-api-key + ``` + + + + ### Configure Provider (Optional) + + The starter kit automatically uses the `ANTHROPIC_API_KEY` environment variable. For advanced configurations (such as proxies or custom headers), refer to the [AI SDK Anthropic documentation](https://sdk.vercel.ai/providers/ai-sdk-providers/anthropic#provider-instance). + + + +## Features + + + + Leverage Anthropic's state-of-the-art Claude models for sophisticated + conversational AI, creative text generation, in-depth analysis, and more + through the intuitive Messages API. + + + + Enable models to understand and process image inputs alongside text for + multimodal applications. + + + + Allow models to interact with external tools and APIs to perform actions and + retrieve real-time information. + + + + Create structured data outputs (like JSON) from natural language prompts, + streamlining the integration of AI capabilities with your existing systems. + + + + Access detailed insights into the model's thought process, enhancing + transparency, debuggability, and trust in AI-generated responses. + + + + (Experimental) Allow models to directly interact with computer desktop + environments to complete complex, multi-step tasks autonomously. + + + +## Use Cases + + + + Craft intelligent, context-aware chatbots capable of nuanced conversations + and sophisticated task completion. Experience this capability in our [Chat + Demo](/ai/docs/chat). + + + + Generate high-quality text for various purposes, or summarize long documents + and conversations accurately. + + + + Extract structured information from unstructured text or analyze complex + data sets combined with visual inputs for comprehensive insights. + + + + Seamlessly integrate Claude models with your existing tools via function + calling to automate complex business processes and tasks. Explore + [Agents](/ai/docs/agents) for advanced implementation options. + + + +## Links + +* [Anthropic Website](https://www.anthropic.com) +* [Anthropic Documentation](https://docs.anthropic.com) +* [AI SDK - Anthropic Provider Docs](https://sdk.vercel.ai/providers/ai-sdk-providers/anthropic) diff --git a/.context/turbostarter-framework-context/sections/ai/docs/api.md b/.context/turbostarter-framework-context/sections/ai/docs/api.md new file mode 100644 index 0000000..92458c7 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/ai/docs/api.md @@ -0,0 +1,52 @@ +--- +title: API +description: Overview of the API service in TurboStarter AI, including its architecture, technology stack, and core functionalities. +url: /ai/docs/api +--- + +# API + +The API service acts as the central hub for all backend logic within TurboStarter AI. It handles interactions with AI models, data processing, and communication between the frontend and backend systems. + +## Technology + +We use [Hono](https://hono.dev), a lightning-fast web framework optimized for edge computing. This ensures efficient handling of API requests—particularly critical for real-time AI interactions like streaming responses. + +**Importantly, this single API layer serves both web and mobile applications, guaranteeing consistent business logic and data handling across all platforms.** + +## AI integration + +While the API package (`@turbostarter/api`) exposes the endpoints, the core AI logic lives in a dedicated package: `@turbostarter/ai`. This package is strictly responsible for: + +* Communicating with various AI providers and models ([OpenAI](/ai/docs/openai), [Anthropic](/ai/docs/anthropic), [Google AI](/ai/docs/google), etc.) +* Processing and formatting data specifically for AI interactions +* Parsing responses from AI models +* Handling AI-specific data storage or retrieval when necessary + +The `@turbostarter/api` package utilizes `@turbostarter/ai` to perform these AI tasks. The API layer itself focuses on registering Hono routes, applying middlewares (like authentication and validation), and exposing AI functionalities to the frontend applications. + +This separation ensures AI-specific logic remains modular and reusable, while the API package stays focused on request handling and routing. + + + API keys for AI services are managed securely on the backend within these packages, ensuring they never appear client-side. + + +## Middlewares + +Hono middlewares streamline request handling by tackling common tasks before the main logic runs. In TurboStarter AI, they handle: + +* **Authentication:** verifying user sessions to protect routes, ensuring only logged-in users access certain features +* **Validation:** using schemas to check if incoming request data (like query parameters or JSON bodies) matches expected formats, preventing invalid data from reaching route handlers +* **Rate limiting:** shielding the API from abuse by restricting the number of requests a user or IP address can make within a given timeframe +* **Credits management:** automatically checking if a user has enough credits for an AI operation and deducting the cost before proceeding +* **Localization:** detecting the user's preferred language to deliver localized responses and error messages + +These middlewares keep core route logic clean and focused, while consistently enforcing security, usage limits, and data integrity across the API. + +## Core API documentation + +For general information about the API setup, architecture, authentication integration, and how to add new endpoints, please refer to the [Core API documentation](/docs/web/api/overview). + + + +Specific configurations related to AI providers or demo apps can be found in their respective documentation sections. diff --git a/.context/turbostarter-framework-context/sections/ai/docs/architecture.md b/.context/turbostarter-framework-context/sections/ai/docs/architecture.md new file mode 100644 index 0000000..d772ea0 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/ai/docs/architecture.md @@ -0,0 +1,121 @@ +--- +title: Architecture +description: A quick overview of the different parts of the TurboStarter AI. +url: /ai/docs/architecture +--- + +# Architecture + +TurboStarter AI integrates several best-in-class open source libraries to power its diverse functionalities, including authentication, data persistence, text generation, and more. Here's a concise overview of the architecture that makes everything work together. + + + +## Application framework + +The project leverages a [monorepo structure](https://turbo.build/repo) powered by [Turborepo](https://turbo.build/) to enable efficient code sharing and consistent tooling across the entire application ecosystem. This approach creates a single source of truth for shared code and dramatically simplifies dependency management. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +### Web + +Built with [Next.js](https://nextjs.org) and [React](https://react.dev), the web application leverages server-side rendering and static site generation for optimal performance and SEO. The UI is styled with [Tailwind CSS](https://tailwindcss.com) and [shadcn/ui](https://ui.shadcn.com) components for rapid development and consistent design. API routes are handled by [Hono](https://hono.dev) for edge computing, chosen for its minimal overhead and excellent TypeScript support. + + + +### Mobile + +The mobile application uses [React Native](https://reactnative.dev) with [Expo](https://expo.dev) for cross-platform development. This combination was selected for its ability to share up to 90% of code between platforms while maintaining native performance. The integration with the monorepo allows seamless sharing of business logic and types with the web application. + + + +## API + +The API is implemented as a dedicated package using [Hono](https://hono.dev), a lightweight framework optimized for edge computing. This architectural decision creates a clear separation between frontend and backend logic, enhancing maintainability and testability. + +Hono's exceptional TypeScript support ensures type safety across all endpoints, while its minimal footprint and edge-first design deliver outstanding performance. + + + +## Model providers + +TurboStarter AI seamlessly integrates with leading AI model providers including [OpenAI](/ai/docs/openai), [Anthropic](/ai/docs/anthropic), [Google AI](/ai/docs/google), [xAI](/ai/docs/xai), and more. The architecture employs [AI SDK](https://sdk.vercel.ai/) to create a unified interface across diverse providers, simplifying experimentation with different models. + +The platform strategically utilizes specialized models for distinct AI tasks: + +* **Text generation** models for conversational AI and content creation +* **Structured output** models for precise data extraction and formatting +* **Image generation** models for visual content creation +* **Voice synthesis** models for natural audio production +* **Embedding** models for semantic search and information retrieval + +Switching models requires just a **one-line code change**, allowing you to rapidly adapt to emerging models or change providers based on your specific requirements. This flexibility ensures your application can leverage the latest AI advancements without extensive refactoring. + +## Authentication + +The applications use [Better Auth](https://www.better-auth.com/) for authentication, providing a secure and flexible authentication system. By default, the AI implementation creates an anonymous user session at startup, which is then used for all subsequent queries and interactions with the AI models. This approach maintains user context across sessions while minimizing friction. + +For more sophisticated authentication requirements, you can easily extend the flow by leveraging the [Core implementation](/docs/web/auth/overview), which supports email/password authentication, magic links, OAuth providers, and more. This modular design lets you implement precisely the level of security your application demands. + + + +## Persistence + +Persistence in TurboStarter AI refers to the system's ability to store and retrieve data from a database. The application uses [PostgreSQL](https://www.postgresql.org/) as its primary database to store critical information such as: + +* Chat history and conversation context +* User accounts and preference settings +* Vector embeddings for retrieval-augmented generation + +To interact with the database from route handlers and server actions, TurboStarter AI leverages [Drizzle ORM](https://orm.drizzle.team/), a high-performance TypeScript ORM that provides type-safe database operations. This ensures robust data integrity and simplified query construction throughout the application. + +A key advantage of Drizzle is its compatibility with multiple database providers including [Neon](https://neon.tech/), [Supabase](https://supabase.com/), and [PlanetScale](https://planetscale.com/). This flexibility allows seamless switching between providers based on your specific requirements without modifying queries or schema definitions — making your application highly adaptable to evolving infrastructure needs. + + + +## Blob storage + +File storage is managed through S3-compatible services, providing scalable, reliable storage for diverse file types. The system efficiently handles user-uploaded images, AI-generated content, and document files. This approach ensures optimal file management and straightforward integration with various storage providers including [AWS S3](https://aws.amazon.com/s3/), [Cloudflare R2](https://www.cloudflare.com/products/r2/), or [MinIO](https://min.io/). + + + +## Security + +Security is implemented comprehensively to protect both the application and its users. All API endpoints incorporate **rate limiting** to prevent abuse and ensure fair resource allocation. + +The system uses a **credits-based access** control system, where each user has a limited number of credits for AI operations, preventing resource exhaustion and enabling monetization options. + +All external API interactions, including those with AI model providers, occur exclusively server-side. This ensures that sensitive API keys are **never exposed** to client-side code, significantly reducing vulnerability to unauthorized access or credential theft. + +Additionally, the system implements industry-standard security practices including thorough input validation, proper authentication enforcement, and regular dependency security audits. + + diff --git a/.context/turbostarter-framework-context/sections/ai/docs/auth.md b/.context/turbostarter-framework-context/sections/ai/docs/auth.md new file mode 100644 index 0000000..6d4f00c --- /dev/null +++ b/.context/turbostarter-framework-context/sections/ai/docs/auth.md @@ -0,0 +1,41 @@ +--- +title: Authentication +description: Learn about the authentication flow in TurboStarter AI. +url: /ai/docs/auth +--- + +# Authentication + +TurboStarter AI implements a streamlined authentication approach powered by [Better Auth](https://www.better-auth.com/). Since the primary focus is showcasing AI capabilities, we've kept the initial authentication simple, allowing you to quickly integrate and experiment with AI features. + +## Anonymous sessions + +When someone first visits the AI application, an **anonymous session** is automatically created. This establishes a unique user identity without requiring login credentials. + +These anonymous sessions serve two critical purposes: + +1. **Persistence:** links data like chat history or generated content to specific users in your database +2. **Usage control:** enables tracking for rate limiting and the credits system, ensuring fair AI resource usage even for anonymous visitors + +## Extending authentication + +While the default anonymous setup provides a frictionless initial experience, TurboStarter is built for growth. The authentication logic uses Better Auth in the shared `packages/auth` package, ensuring consistency between web and mobile applications. + +When your project needs more sophisticated authentication features like: + +* Email/Password login +* Magic links +* Social logins (OAuth) +* Multi-factor authentication + +You can easily integrate these by leveraging the comprehensive authentication system in the [TurboStarter Core kit](/docs/web). The underlying structure is already in place, making this transition straightforward. + +For detailed implementation guides, check out the core documentation: + + + + + + + +By starting with anonymous sessions, the AI kit lets you focus on building compelling AI features first, while providing a clear path to implement advanced user management and security as your application evolves. diff --git a/.context/turbostarter-framework-context/sections/ai/docs/billing.md b/.context/turbostarter-framework-context/sections/ai/docs/billing.md new file mode 100644 index 0000000..7a30f5a --- /dev/null +++ b/.context/turbostarter-framework-context/sections/ai/docs/billing.md @@ -0,0 +1,42 @@ +--- +title: Billing +description: Discover how to manage billing and payment methods for AI features. +url: /ai/docs/billing +--- + +# Billing + +TurboStarter AI includes a straightforward middleware setup to manage user credits for AI features. This lets you control access based on available credits without complex payment integrations. + +## Credit-based access + +A focused middleware verifies if users have enough credits before allowing them to access specific AI-powered routes or actions. + +```ts title="ai.router.ts" +export const aiRouter = new Hono().post( + "/chat", + rateLimiter, + validate("json", chatMessageSchema), + deductCredits({ + amount: 10, // [!code highlight] + }), + streamChat, +); +``` + +This example shows how the `deductCredits` middleware subtracts a specific amount (10 credits) for each request to the `/chat` endpoint. + +## Coming soon + +We're actively expanding the billing capabilities for AI services, including: + +* **Usage-based billing:** implementing a system where users pay based on their actual consumption of AI resources (tokens used, API calls made, etc.) +* **Payment provider integration:** connecting with popular services like [Stripe](/docs/web/billing/stripe), [Lemon Squeezy](/docs/web/billing/lemon-squeezy), and more for hassle-free payment processing + +## Extending billing + +For more advanced billing scenarios or immediate needs, you can tap into the core TurboStarter billing features. The main documentation provides detailed guidance on setting up and managing billing with third-party providers. + + + +Stay tuned for updates as we enhance the AI-specific billing functionalities! diff --git a/.context/turbostarter-framework-context/sections/ai/docs/chat.md b/.context/turbostarter-framework-context/sections/ai/docs/chat.md new file mode 100644 index 0000000..cb1f71a --- /dev/null +++ b/.context/turbostarter-framework-context/sections/ai/docs/chat.md @@ -0,0 +1,168 @@ +--- +title: Chatbot +description: Build a powerful AI assistant with multiple LLMs, generative UI, web browsing, and image analysis. +url: /ai/docs/chat +--- + +# Chatbot + +The [Chatbot](https://ai.turbostarter.dev/chat) demo application showcases an advanced AI assistant capable of engaging in complex conversations, performing web searches, and understanding context. It integrates multiple large language models (LLMs) and allows users to attach files to the chat window. + + + +## Features + +The chatbot offers a variety of capabilities for an enhanced conversational experience: + + + + Switch effortlessly between leading AI providers like + [OpenAI](/ai/docs/openai) and [Anthropic](/ai/docs/anthropic) within a + single, consistent chat interface. + + + + Experience an AI that truly understands complex questions and delivers + thoughtful, nuanced responses based on comprehensive reasoning. + + + + Access up-to-the-minute information directly from the web through the + integrated search capability powered by [Tavily AI](https://tavily.com/). + + + + Enrich conversations by sharing and analyzing files, images, or web links + directly within the chat interface for contextual discussion. + + + + Enjoy natural, fluid conversations with responses that stream in real-time, + eliminating waiting periods. + + + + Seamlessly manage your conversation history with features to save, organize, + and revisit previous discussions. + + + +## Setup + +To implement your advanced AI assistant, you'll need several services configured. If you haven't set these up yet, start with: + + + + + + + +### AI models + + + Different models offer varying capabilities for tool calling, reasoning, and file processing. Consider these differences when selecting the optimal model for your specific use case. + + +The Chatbot leverages the AI SDK to support various language and vision models. You can easily switch between models based on your needs. Explore the documentation for the most popular models: + + + } /> + + } /> + + } /> + + } /> + + +For detailed configuration of specific providers and other supported models, refer to the [AI SDK documentation](https://sdk.vercel.ai/providers/ai-sdk-providers). + +### Web browsing + +The chatbot utilizes [Tavily AI](https://tavily.com/) to provide real-time web search capabilities. Tavily is a specialized search engine optimized for LLMs and AI agents, designed to deliver highly relevant search results by automatically handling the complexities of web scraping, filtering, and extracting relevant information. + +We selected Tavily because it dramatically simplifies the integration of current web data into AI applications through a single API call that returns comprehensive, AI-ready search results. + + + Tavily offers a generous free tier with [1,000 API credits per + month](https://docs.tavily.com/documentation/api-credits) without requiring + credit card information. A basic search consumes 1 credit, while an advanced + search uses 2 credits. Paid plans are available for higher volume usage. + + +To enable web browsing, follow these steps: + + + + #### Get Tavily API Key + + Sign up or log in at the [Tavily Platform](https://app.tavily.com/sign-in) to obtain your API key from the dashboard. + + + + #### Add API Key to Environment + + Add your API key to your project's `.env` file (e.g., in `apps/web`): + + ```bash title=".env" + TAVILY_API_KEY=tvly-your-api-key + ``` + + + +With the API key properly configured, the chatbot will automatically utilize Tavily for searches when contextually appropriate. + +## Data persistence + +User interactions and chat history are persisted to ensure a continuous experience across sessions. + + + +Conversation data is organized within a dedicated PostgreSQL schema named `chat` +to maintain clear separation from other application data. + +* `chats`: stores records for each conversation session, including essential metadata like user ID and creation timestamp. +* `messages`: maintains the content of individual messages exchanged within conversations, linked to their parent chat session. +* `parts`: handles complex message structures by breaking down content into smaller components, particularly useful for generative UI elements or multi-modal content. + + + +Files shared within conversations (such as images or documents) are uploaded to [cloud storage](/ai/docs/storage) (S3-compatible), with references to these attachments stored within the message content or parts. + +## Structure + +The Chatbot functionality is thoughtfully distributed across shared packages and platform-specific code for web and mobile, ensuring optimal code reuse and consistency. + +### Core + +The `@turbostarter/ai` package, located in `packages/ai`, contains the central chat functionality in the `src/chat` directory. It includes: + +* Essential constants, types, and validation schemas for chat interactions +* Core API logic for managing conversations and messages +* Comprehensive chat history persistence and retrieval functionality +* AI model provider configuration and initialization +* Integrations for external tools like web search + +### API + +Built with Hono, the `packages/api` package defines all API endpoints. Chat-specific routes are organized under `src/modules/ai/chat`: + +* `chat.router.ts`: establishes Hono RPC routes, handles input validation, and connects frontend requests to the core AI logic in `packages/ai` +* Manages authentication, request processing, and database interactions through the core package + +### Web + +The Next.js web application in `apps/web` implements the user-facing chat interface: + +* `src/app/[locale]/(apps)/chat/**`: contains the Next.js App Router pages and layouts dedicated to the chat experience +* `src/components/chat/**`: houses reusable React components for the chat interface (message bubbles, input area, model selector, etc.) + +### Mobile + +The Expo/React Native mobile application in `apps/mobile` delivers a native chat experience: + +* `src/app/chat/**`: defines the primary screens for the mobile chat interface +* `src/components/chat/**`: contains React Native components styled to match the web version, optimized for mobile interaction +* **API interaction**: utilizes the same Hono RPC client (`packages/api`) as the web app for consistent backend communication + +This modular structure promotes separation of concerns and facilitates independent development and scaling of different parts of the application. diff --git a/.context/turbostarter-framework-context/sections/ai/docs/database.md b/.context/turbostarter-framework-context/sections/ai/docs/database.md new file mode 100644 index 0000000..3858b64 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/ai/docs/database.md @@ -0,0 +1,44 @@ +--- +title: Database +description: Overview of the database service in TurboStarter AI. +url: /ai/docs/database +--- + +# Database + +The database service, managed within the `packages/db` directory (as `@turbostarter/db`), stores data essential for both core application functions and AI features. It ensures that information like user profiles, conversation history, and AI-generated content is reliably preserved and efficiently accessed. + +## Technology + +We've chosen [PostgreSQL](https://www.postgresql.org) as our primary relational database for its exceptional reliability, extensibility (including powerful tools like `pgvector` for similarity searches), and proven track record in production environments. + +Database interactions are handled through [Drizzle ORM](https://orm.drizzle.team/), a cutting-edge TypeScript ORM that offers outstanding type safety (generating types directly from your schema), high performance, and a developer-friendly API. + +For detailed guidance on setup, configuration, schema management (including migrations), and general usage patterns of Drizzle and PostgreSQL in the TurboStarter ecosystem, check out our core documentation: + + + + + + + + + + + +## What is stored in the database? + +Beyond standard application data (like users and accounts), the database plays a crucial role in storing AI-specific information: + +* **Chat history**: saves conversations between users and AI models (including reasoning and usage details), enabling continuous conversations and history features +* **Vector embeddings**: stores numerical representations (vectors) of text data (like document chunks) that power Retrieval-Augmented Generation (RAG) techniques, allowing features like [Chat with PDF](/ai/docs/pdf) to quickly find relevant context from large document collections +* **Document references**: tracks metadata and storage identifiers (paths in [Blob Storage](/ai/docs/storage)) for files like uploaded PDFs or AI-generated images, connecting them to relevant user interactions +* **Tool calls & results**: records actions (such as [web searches](/ai/docs/chat) or calculations) that AI models ([Agents](/ai/docs/agents)) perform, along with their outcomes—valuable for debugging, auditing, and improving agent capabilities + +## Schema + +The core database schema, defined in `packages/db/src/schema`, contains essential tables for the overall application (users, accounts, sessions, etc.). + +To maintain clarity as AI features grow, tables specifically related to AI demo applications (like chat history for the [PDF app](/ai/docs/pdf)) are often placed in dedicated [PostgreSQL schemas](https://www.postgresql.org/docs/current/ddl-schemas.html) (e.g. a schema named `pdf`). + +This logical separation helps manage complexity and isolates feature-specific data structures. You'll typically find AI-specific schema definitions either alongside the relevant demo app code or within the main `packages/db/src/schema` directory, clearly labeled and organized. diff --git a/.context/turbostarter-framework-context/sections/ai/docs/deepseek.md b/.context/turbostarter-framework-context/sections/ai/docs/deepseek.md new file mode 100644 index 0000000..c96d528 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/ai/docs/deepseek.md @@ -0,0 +1,85 @@ +--- +title: DeepSeek +description: Integrate DeepSeek's powerful AI models into your applications with minimal setup. +url: /ai/docs/deepseek +--- + +# DeepSeek + +The [DeepSeek](https://www.deepseek.com/) provider delivers access to DeepSeek's advanced AI models through the AI SDK, bringing reasoning capabilities to your applications. + +![DeepSeek](/images/docs/ai/providers/deepseek.webp) + +## Setup + + + + ### Generate API Key + + Visit the [DeepSeek Platform](https://platform.deepseek.com/) and navigate to the API keys section to create your personal secret key. + + + + ### Add API Key to Environment + + Add your generated API key to your project's `.env` file (e.g., in `apps/web`): + + ```bash title=".env" + DEEPSEEK_API_KEY=your-api-key + ``` + + + + ### Configure Provider (Optional) + + The starter kit automatically utilizes the `DEEPSEEK_API_KEY` environment variable. For advanced configurations, consult the comprehensive [AI SDK DeepSeek documentation](https://sdk.vercel.ai/providers/ai-sdk-providers/deepseek#provider-instance). + + + +## Features + + + + Utilize DeepSeek's language models, known for their deep reasoning + capabilities, for tasks like text generation, translation, and + conversational AI applications. + + + + Tap into models with reasoning abilities designed specifically for complex + problem-solving, logical deduction, and analytical tasks that require deep + understanding. + + + + Enable language models to interact with external tools and functions, + allowing for more complex and automated task execution. + + + +## Use Cases + + + + Create intelligent chatbots that engage in natural, meaningful conversations + and assist users with a wide range of tasks. Experience this capability in + our [Chat Demo](/ai/docs/chat). + + + + Produce diverse, high-quality creative text content including articles, + summaries, code explanations, and marketing copy with language + understanding. + + + + Integrate language models with other tools via function calling to automate + processes like data analysis or report generation. + + + +## Links + +* [DeepSeek Website](https://www.deepseek.com/) +* [DeepSeek Platform](https://platform.deepseek.com/) +* [AI SDK - DeepSeek Provider Docs](https://sdk.vercel.ai/providers/ai-sdk-providers/deepseek) diff --git a/.context/turbostarter-framework-context/sections/ai/docs/eleven-labs.md b/.context/turbostarter-framework-context/sections/ai/docs/eleven-labs.md new file mode 100644 index 0000000..60c128b --- /dev/null +++ b/.context/turbostarter-framework-context/sections/ai/docs/eleven-labs.md @@ -0,0 +1,143 @@ +--- +title: Eleven Labs +description: Setup ElevenLabs and learn how to integrate its AI audio capabilities into the starter kit. +url: /ai/docs/eleven-labs +--- + +# Eleven Labs + +[ElevenLabs](https://elevenlabs.io/) stands at the forefront of AI audio innovation, specializing in ultra-realistic Text-to-Speech (TTS), voice cloning, and advanced audio generation. While not a native provider within the AI SDK core, ElevenLabs' powerful services integrate seamlessly with AI applications to deliver exceptional voice experiences. + +![ElevenLabs](/images/docs/ai/providers/elevenlabs.jpg) + +## Setup + +Integrating ElevenLabs involves using their purpose-built SDKs (Python, TypeScript/JavaScript) alongside your application logic: + + + + ### Generate API Key + + Visit the [ElevenLabs website](https://elevenlabs.io/), create an account or sign in, then navigate to your profile settings to generate your unique API key. + + + + ### Add API Key to Environment + + Add your API key to your project's `.env` file (e.g., in `apps/web` or the appropriate package): + + ```bash title=".env" + ELEVENLABS_API_KEY=your-api-key + ``` + + + + ### Configure SDK + + Initialize the ElevenLabs client with your API key: + + ```typescript title="client.ts" + import { ElevenLabsClient } from "elevenlabs"; + + import { env } from "../../env"; + + export const client = new ElevenLabsClient({ + apiKey: env.ELEVENLABS_API_KEY, + }); + // Now use the client object... + ``` + + For comprehensive implementation details, refer to the [ElevenLabs Quickstart Guide](https://elevenlabs.io/docs/quickstart). + + + +## Features + +ElevenLabs offers a comprehensive suite of AI audio technologies: + + + + Transform written text into remarkably natural speech across numerous + languages, voices, and styles, with flexible options for quality or + low-latency delivery. + + + + Transcribe spoken audio into text accurately, supporting multiple languages + and providing features like speaker diarization. + + + + Create stunningly accurate digital replicas of voices from audio samples, + with both instant and professional-grade options to suit your needs. + + + + Craft entirely new, unique synthetic voices based on descriptive parameters, + enabling custom voice creation without requiring sample recordings. + + + + Build and deploy end-to-end conversational voice agents, integrating STT, + LLMs (like GPT, Claude, Gemini), TTS, and turn-taking logic. + + + + Automatically dub audio or video content into different languages while + preserving the original voice characteristics. + + + + Create custom sound effects and ambient audio from simple text descriptions, + adding rich audio elements to your applications. + + + + Access an extensive collection of pre-made, ready-to-use voices contributed + by the ElevenLabs community. + + + +## Use Cases + + + + Power conversational AI applications like customer service bots, virtual + assistants, or interactive characters with low-latency TTS. + + + + Create professional-quality narration for audiobooks, articles, videos, and + e-learning content in multiple languages and voices. Experience this in the + [TTS Demo](/ai/docs/tts). + + + + Enhance digital accessibility by converting text content into natural + speech, making your applications more inclusive for users with visual + impairments or reading difficulties. + + + + Deliver dynamic, personalized audio experiences with custom-designed or + cloned voices, creating unique and engaging user interactions. + + + + Utilize dubbing and multilingual TTS to easily adapt content for + international audiences. + + + + Generate character voices, ambient sounds, and dynamic audio for immersive + experiences. + + + +## Links + +* [ElevenLabs Website](https://elevenlabs.io/) +* [ElevenLabs Documentation](https://elevenlabs.io/docs) +* [Developer Quickstart](https://elevenlabs.io/docs/quickstart) +* [API Reference](https://elevenlabs.io/docs/api-reference/introduction) +* [Pricing](https://elevenlabs.io/pricing) diff --git a/.context/turbostarter-framework-context/sections/ai/docs/google.md b/.context/turbostarter-framework-context/sections/ai/docs/google.md new file mode 100644 index 0000000..7469bd0 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/ai/docs/google.md @@ -0,0 +1,121 @@ +--- +title: Google AI +description: Setup Google Generative AI provider and learn how to use its models like Gemini in the starter kit. +url: /ai/docs/google +--- + +# Google AI + +The [Google Generative AI](https://ai.google/) provider integrates Google's state-of-the-art models, including the versatile Gemini family, into your applications through the AI SDK. + +![Google Generative AI](/images/docs/ai/providers/google.webp) + +## Setup + + + + ### Generate API Key + + Visit the [Google AI Studio](https://aistudio.google.com/app/apikey) to create your API key. For enterprise applications using Google Cloud, you can alternatively configure authentication via Application Default Credentials or service accounts. + + + + ### Add API Key to Environment + + Add your API key to your project's `.env` file (e.g., in `apps/web`): + + ```bash title=".env" + GOOGLE_GENERATIVE_AI_API_KEY=your-api-key + ``` + + If using Google Cloud credentials instead, ensure they're properly configured in your environment. + + + + ### Configure Provider (Optional) + + The starter kit automatically uses the `GOOGLE_GENERATIVE_AI_API_KEY` environment variable. For advanced configurations (such as proxies, custom API versions, or specific headers), you can create a tailored provider instance using `createGoogleGenerativeAI`. See the [AI SDK Google documentation](https://sdk.vercel.ai/providers/ai-sdk-providers/google-generative-ai#provider-instance) for comprehensive details. + + + +## Features + + + + Leverage Google's advanced Gemini models for chat, text generation, + reasoning, and complex instruction following. + + + + Utilize text embedding models to convert text into numerical representations + for tasks like semantic search, clustering, and RAG. + + + + Analyze and understand various file types (including images and PDFs) + alongside text prompts, enabling rich multimodal applications with + comprehensive content understanding. + + + + Empower models to interact seamlessly with external tools and APIs, allowing + them to perform real-world actions and retrieve up-to-date information for + more capable applications. + + + + Configure safety thresholds to control model responses regarding harmful + content categories. Access safety ratings in the response metadata. + + + + Cache content to optimize context reuse and potentially reduce latency and + costs for repeated queries with similar context. + + + + (With compatible models) Ground responses in real-time search results, + dramatically enhancing factual accuracy and providing up-to-date information + on current topics. + + + +## Use Cases + + + + Create sophisticated conversational agents powered by Gemini models that can + engage in natural dialogue and handle complex, multi-step tasks. Experience + this in our [Chat Demo](/ai/docs/chat). + + + + Generate diverse text formats, from creative writing and marketing copy to + code explanations and summaries. + + + + Build applications that seamlessly analyze and understand images, documents, + and other file types alongside text, creating richer, more contextual user + experiences. + + + + Implement powerful search capabilities or sophisticated Retrieval-Augmented + Generation systems using Google's high-performance embedding models for more + accurate information retrieval. + + + + Streamline operations by connecting language models to external tools and + APIs through function calling, automating complex business processes and + repetitive tasks with minimal human intervention. + + + +## Links + +* [Google AI](https://ai.google/) +* [Google AI Studio](https://aistudio.google.com/) +* [Google Generative AI Documentation](https://ai.google.dev/docs) +* [AI SDK - Google Provider Docs](https://sdk.vercel.ai/providers/ai-sdk-providers/google-generative-ai) diff --git a/.context/turbostarter-framework-context/sections/ai/docs/image.md b/.context/turbostarter-framework-context/sections/ai/docs/image.md new file mode 100644 index 0000000..448efd5 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/ai/docs/image.md @@ -0,0 +1,116 @@ +--- +title: Image Generation +description: Learn how to generate images using AI models within the TurboStarter AI demo application. +url: /ai/docs/image +--- + +# Image Generation + +The [Image Generation](https://ai.turbostarter.dev/image) demo application allows users to create unique visuals from textual descriptions using various AI models. It provides a simple interface to input prompts, select models, and view generated images. + + + +## Features + +Explore the capabilities of the AI-powered image generation tool: + + + + Create images simply by describing what you want to see in text. + + + + Choose from different AI image generation models offered by various + providers. + + + + Select the desired aspect ratio for your generated images (e.g. square, + landscape, portrait). + + + + Create multiple design variations from a single prompt simultaneously, + accelerating your creative workflow. + + + + Access and reference your complete generation history, including all prompts + and resulting images for continued iteration. + + + +## Setup + +To implement image generation in your application, you'll need to configure the necessary backend services. + + + + + + + +You'll also need API keys for your preferred AI models. Follow the detailed setup instructions in the provider documentation linked below. + +## AI models + +The Image Generation app leverages the AI SDK to support various models capable of creating images from text. Configure the providers for the models you wish to use: + + + } /> + + } /> + + +For detailed implementation guidance, refer to the [AI SDK documentation](https://sdk.vercel.ai/docs/ai-sdk-core/image-generation) covering the `generateImage` function and supported providers. + +## Data persistence + +Details about image generation requests and the resulting images are stored to maintain user history. + + + +Data is organized within a dedicated PostgreSQL schema named `image`: + +* `generations`: captures detailed information about each generation request, including the `prompt`, selected `model`, `aspectRatio`, requested image `count`, `userId`, and precise timestamps. +* `images`: stores complete metadata for each generated image, linked to its parent `generation` record via `generationId` and maintaining the `url` reference to the stored image file. + + + +The generated image files are securely stored in [cloud storage](/ai/docs/storage) (S3-compatible). Each image's location is tracked via the `url` field in the `images` table for reliable retrieval. + +## Structure + +The Image Generation feature is architected across the monorepo for optimal code organization and reusability. + +### Core + +The `@turbostarter/ai` package (`packages/ai`) contains the essential logic under `modules/image`: + +* Comprehensive types, validation schemas (for prompts, aspect ratios, etc.), and constants +* Core API logic for processing image generation requests and interfacing with AI models +* Database operations for recording generation details and image metadata +* Utilities for uploading generated images to cloud storage + +### API + +The `packages/api` package defines the backend API endpoints using Hono: + +* `src/modules/ai/image/image.router.ts`: implements Hono RPC routes for image generation, handles input validation, applies necessary middleware (authentication, credit management), and invokes the core logic from `@turbostarter/ai`. + +### Web + +The Next.js application (`apps/web`) delivers an intuitive user interface: + +* `src/app/[locale]/(apps)/image/**`: contains the Next.js App Router pages and layouts for the image generation experience +* `src/components/image/**`: houses reusable React components tailored to the image generation UI (prompt input, model selector, image gallery, etc.) + +### Mobile + +The Expo/React Native application (`apps/mobile`) provides a native mobile experience: + +* `src/app/image/**`: defines the screens for the mobile image generation interface +* `src/components/image/**`: contains React Native components optimized for mobile interaction +* **API integration**: utilizes the same Hono RPC client (`packages/api`) as the web app for consistent backend communication + +This architecture ensures perfect consistency across platforms while enabling tailored UI implementations optimized for each environment. diff --git a/.context/turbostarter-framework-context/sections/ai/docs/internationalization.md b/.context/turbostarter-framework-context/sections/ai/docs/internationalization.md new file mode 100644 index 0000000..a1ab1df --- /dev/null +++ b/.context/turbostarter-framework-context/sections/ai/docs/internationalization.md @@ -0,0 +1,39 @@ +--- +title: Internationalization +description: Learn how we manage internationalization in TurboStarter AI. +url: /ai/docs/internationalization +--- + +# Internationalization + +TurboStarter AI builds on the core internationalization (i18n) setup from the main TurboStarter framework. The shared `@turbostarter/i18n` package in `packages/i18n` handles translation management across platforms. + +This gives you the benefit of a proven system using [i18next](https://www.i18next.com/) for managing translations on both web and mobile apps. Plus, the AI models and LLMs integrated within TurboStarter AI generally support multiple languages, enabling interactions beyond what's covered by UI translations alone. + +For detailed information on configuring languages, adding translations, or using the `useTranslation` hook, check out the core documentation: + + + + + + + +## AI-specific translations + +While most translations are shared across the platform, TurboStarter AI introduces a dedicated `ai` namespace within translation files. This namespace contains strings specifically for AI features, demo applications, and UI elements unique to the AI starter kit. + +```json title="packages/i18n/locales/en/ai.json" +{ + "chat": { + "title": "AI Chatbot", + "description": "Engage in intelligent conversations." + }, + "image": { + "title": "Image Generation", + "description": "Create stunning visuals with AI." + } + // ... other AI-specific translations +} +``` + +When adding translations for new AI features or modifying existing ones, place them within the `ai` namespace in the appropriate language files (e.g., `en/ai.json`, `es/ai.json`). This keeps AI-related text organized and separate from core application translations. diff --git a/.context/turbostarter-framework-context/sections/ai/docs/meta.md b/.context/turbostarter-framework-context/sections/ai/docs/meta.md new file mode 100644 index 0000000..fc001ed --- /dev/null +++ b/.context/turbostarter-framework-context/sections/ai/docs/meta.md @@ -0,0 +1,125 @@ +--- +title: Meta +description: Setup Meta's Llama models and learn how to use them in the starter kit via various hosting providers. +url: /ai/docs/meta +--- + +# Meta + +The [Meta](https://ai.meta.com/) provider integration brings Meta's cutting-edge Llama family of open-weight models to your applications through the AI SDK. Renowned for their exceptional performance across diverse tasks, these models deliver state-of-the-art capabilities for your AI solutions. + +![Meta Llama](/images/docs/ai/providers/meta.jpg) + +## Setup + +Deploying Llama models in your applications involves leveraging a third-party hosting provider that integrates seamlessly with the AI SDK, such as DeepInfra, Fireworks AI, Amazon Bedrock, Baseten, and others. + + + + ### Choose a hosting provider & get API Key + + Select a trusted provider that hosts Llama models (e.g., [DeepInfra](https://deepinfra.com/), [Fireworks AI](https://fireworks.ai/), or [Amazon Bedrock](https://aws.amazon.com/bedrock/)). Register with your preferred provider and generate a secure API key through their platform console. + + + + ### Add API Key to environment + + Add your provider-specific API key to your project's `.env` file (e.g., in `apps/web`). Use the appropriate environment variable for your chosen provider: + + ```bash title=".env" + # Example for DeepInfra + DEEPINFRA_API_KEY=your-deepinfra-api-key + + # Example for Fireworks AI + FIREWORKS_API_KEY=your-fireworks-api-key + + # Example for Amazon Bedrock (requires AWS credentials) + # AWS_ACCESS_KEY_ID=... + # AWS_SECRET_ACCESS_KEY=... + # AWS_REGION=... + ``` + + + + ### Configure provider + + When implementing AI SDK functions (`generateText`, `streamText`, etc.), initialize the client for your selected provider and specify the appropriate Llama model identifier: + + ```ts + import { generateText } from "ai"; + import { deepinfra } from "@ai-sdk/deepinfra"; + // Or: import { fireworks } from '@ai-sdk/fireworks'; + // Or: import { bedrock } from '@ai-sdk/amazon-bedrock'; + + const { text } = await generateText({ + // Example using DeepInfra + model: deepinfra("meta-llama/Meta-Llama-3.1-8B-Instruct"), + // Example using Fireworks AI + // model: fireworks('accounts/fireworks/models/llama-v3p1-8b-instruct'), + // Example using Amazon Bedrock + // model: bedrock('meta.llama3-1-8b-instruct-v1:0'), + prompt: "Why is the sky blue?", + }); + ``` + + For comprehensive implementation details, consult the AI SDK documentation for your specific provider: [DeepInfra](https://sdk.vercel.ai/providers/ai-sdk-providers/deepinfra), [Fireworks AI](https://sdk.vercel.ai/providers/ai-sdk-providers/fireworks), [Amazon Bedrock](https://sdk.vercel.ai/providers/ai-sdk-providers/amazon-bedrock), etc. + + + +## Features + +Llama models accessible through the AI SDK offer a range of powerful capabilities, with specific features varying based on model version and hosting provider implementation. + + + + Utilize Llama's instruction-tuned models for dialogue generation, + translation, reasoning, and other conversational tasks. Available in various + sizes (e.g., 8B, 70B, 405B). + + + + Empower Llama models to interact with external tools and functions, enabling + complex, multi-step task execution and real-world system integration. + (Capabilities may vary depending on your selected provider). + + + + Leverage Llama's capabilities for complex reasoning problems and generating + code snippets in various programming languages. + + + +## Use Cases + + + + Create intelligent, responsive chatbots capable of natural conversations, + accurate information retrieval, and efficient task execution. Experience + this capability in our [Chat Demo](/ai/docs/chat). + + + + Produce diverse, high-quality text content spanning articles, summaries, + creative narratives, marketing copy, and more—tailored to your specific + requirements. + + + + Boost developer productivity with AI-powered code generation, insightful + code explanations, effective debugging assistance, and programming guidance + across multiple languages. + + + + Streamline operations by combining Llama models with tool usage capabilities + to automate complex business processes and seamlessly interact with your + existing systems. + + + +## Links + +* [Meta AI](https://ai.meta.com/) +* [Meta Llama Models](https://ai.meta.com/llama/) +* [AI SDK - Llama 3.1 Guide](https://sdk.vercel.ai/docs/guides/llama-3_1) +* [AI SDK - Providers](https://sdk.vercel.ai/providers) (Find hosting provider docs here) diff --git a/.context/turbostarter-framework-context/sections/ai/docs/openai.md b/.context/turbostarter-framework-context/sections/ai/docs/openai.md new file mode 100644 index 0000000..da796be --- /dev/null +++ b/.context/turbostarter-framework-context/sections/ai/docs/openai.md @@ -0,0 +1,122 @@ +--- +title: OpenAI +description: Setup OpenAI provider and learn how to use it in the starter kit. +url: /ai/docs/openai +--- + +# OpenAI + +The [OpenAI](https://openai.com) provider integrates OpenAI's powerful suite of language models, image generation capabilities, and embedding technologies into your application through the AI SDK. + +![OpenAI](/images/docs/ai/providers/openai.png) + +## Setup + + + + ### Generate API Key + + Visit the [OpenAI API keys page](https://platform.openai.com/api-keys) to create your personal secret key for API access. + + + + ### Add API Key to Environment + + Add your API key to your project's `.env` file (e.g., in `apps/web`): + + ```bash title=".env" + OPENAI_API_KEY=your-api-key + ``` + + + + ### Configure Provider (Optional) + + By default, the starter kit automatically uses the `OPENAI_API_KEY` environment variable. For advanced configurations (such as using a proxy or specific organization ID), you can customize the provider instance. For detailed options, refer to the [AI SDK OpenAI documentation](https://sdk.vercel.ai/providers/ai-sdk-providers/openai#provider-instance). + + + +## Features + + + + Leverage state-of-the-art models for building sophisticated conversational + AI, generating creative text formats, and answering complex questions. + + + + Transform text into rich numerical representations with powerful models like + `text-embedding-3-large`, enabling advanced semantic search, intelligent + text clustering, and highly personalized recommendation systems. + + + + Generate unique images from textual descriptions using OpenAI's DALL·E + models, enabling creative applications and content generation. + + + + Convert written text into natural-sounding human speech with various voices + using Text-to-Speech (TTS) models, ideal for accessibility features or voice + interfaces. + + + + Empower models like GPT-4o or GPT-4 Turbo with Vision capabilities to + understand, analyze, and describe the content of images provided in prompts. + + + + Allow language models to intelligently interact with your external tools, + APIs, and custom functions, orchestrating complex multi-step tasks and + creating powerful AI agents that can take actions in the real world. + + + +## Use Cases + + + + Create intelligent, context-aware conversational agents that engage in + natural dialogue, answer complex questions, and complete sophisticated tasks + based on user needs. Experience this capability in our [Chat + Demo](/ai/docs/chat). + + + + Automate the creation of diverse text-based content, including blog posts, + marketing copy, emails, code snippets, and creative writing pieces. + + + + Build advanced search systems that truly understand the meaning behind user + queries, enhanced with Retrieval-Augmented Generation (RAG) for delivering + exceptionally accurate, contextually relevant answers from your data. + + + + Develop applications that can generate images from text prompts or analyze + and interpret the content of existing images for tagging, description, or + moderation. Check out the [Image Generation Demo](/ai/docs/image). + + + + Design engaging voice-enabled experiences, including lifelike virtual + assistants, expressive audiobook narration, real-time translation services, + and accessibility tools that convert text to natural speech for visually + impaired users. + + + + Transform business processes by connecting powerful language models to your + existing tools and systems through function calling, automating complex + workflows for data processing, report generation, customer support, and + more. + + + +## Links + +* [OpenAI Website](https://openai.com/) +* [OpenAI API Documentation](https://platform.openai.com/docs) +* [AI SDK - OpenAI Provider Docs](https://sdk.vercel.ai/providers/ai-sdk-providers/openai) diff --git a/.context/turbostarter-framework-context/sections/ai/docs/pdf.md b/.context/turbostarter-framework-context/sections/ai/docs/pdf.md new file mode 100644 index 0000000..f8b6bba --- /dev/null +++ b/.context/turbostarter-framework-context/sections/ai/docs/pdf.md @@ -0,0 +1,135 @@ +--- +title: Chat with PDF +description: Engage in conversations with your PDF documents using AI to extract insights and answer questions. +url: /ai/docs/pdf +--- + +# Chat with PDF + +The [Chat with PDF](https://ai.turbostarter.dev/pdf) demo application enables intelligent interaction with document content through a conversational AI interface. Upload PDF files and instantly engage in natural dialogue about their contents, asking questions, requesting summaries, and extracting key information with remarkable accuracy. + + + +## Features + +Transform how you interact with document content through these powerful capabilities: + + + + Easily upload PDF files directly into the application for analysis. + + + + Chat with an AI that understands the content of your uploaded PDF, providing + relevant answers based on the text. + + + + Quickly find specific information, key points, or summaries within the + document through natural language queries. + + + + Visualize exactly which document sections informed the AI's responses with + precise source highlighting. + + + + Conduct sophisticated conversations spanning multiple uploaded documents, + enabling cross-document analysis and comparison. + + + +## Setup + +To implement the "Chat with PDF" application in your project, configure these essential backend services: + + + + Set up PostgreSQL with the `pgvector` extension to efficiently store + conversation history, document metadata, and vector embeddings for semantic + search. + + + + Configure S3-compatible cloud storage for secure management of uploaded PDF + documents. + + + +You'll also need to obtain API keys for both the conversational AI models and the embedding models used for text processing. + +## AI models + +This application leverages two complementary AI model types working together: + +1. **Large Language Models (LLMs):** Provide sophisticated natural language understanding to interpret your questions and generate contextually appropriate responses based on document content. +2. **Embedding Models:** Convert document text segments into numerical vector representations that enable efficient semantic similarity search and [Retrieval-Augmented Generation (RAG)](https://en.wikipedia.org/wiki/Retrieval-augmented_generation). + +Configure the providers for the models you wish to use: + + + } /> + + } /> + + } /> + + } /> + + +For comprehensive configuration details, consult the [AI SDK documentation](https://sdk.vercel.ai/docs) covering provider setup and model selection. + +## Data persistence + +The application stores data related to chats, documents, and embeddings to provide a persistent experience. + + + +Application data is organized within a dedicated PostgreSQL schema named `pdf`: + +* `chats`: captures essential metadata for each document-specific conversation session. +* `messages`: stores all user queries and AI responses within conversation threads. +* `documents`: maintains comprehensive tracking of uploaded PDF files, including filenames and storage locations. +* `embeddings`: contains text segments extracted from PDFs along with their vector representations (using [`pgvector`](https://github.com/pgvector/pgvector)'s `vector` data type). To optimize similarity searches critical for RAG processing, the system creates an index (`embeddingIndex` using [HNSW](https://github.com/pgvector/pgvector#hnsw)) on the `embedding` column. + + + +The PDF files uploaded by users are securely stored in your configured [cloud storage](/ai/docs/storage) bucket. The `path` field in the `documents` table maintains the precise reference to each file's location. + +## Structure + +The "Chat with PDF" feature is architected across the monorepo for optimal organization and code reuse: + +### Core + +The `@turbostarter/ai` package (`packages/ai`) contains the essential logic under `modules/pdf`: + +* Comprehensive types, validation schemas, and constants specific to PDF processing +* Advanced document parsing, text segmentation, and embedding generation utilities +* Core API logic for managing conversations, performing RAG-based lookups, and interacting with LLMs +* Database operations for storing and retrieving conversations, documents, and embeddings +* Shared utilities for managing PDF file uploads and downloads + +### API + +The `packages/api` package defines the backend API endpoints using [Hono](https://hono.dev/): + +* `src/modules/ai/pdf/pdf.router.ts`: implements Hono RPC routes for document upload and conversation management, handles input validation, applies middleware (authentication, credit management), and invokes the core functionality from `@turbostarter/ai`. + +### Web + +The [Next.js](https://nextjs.org/) application (`apps/web`) delivers an intuitive user interface: + +* `src/app/[locale]/(apps)/pdf/**`: contains the Next.js App Router pages and layouts for the document conversation experience +* `src/components/pdf/**`: houses reusable React components specific to the PDF interaction UI (document upload, conversation interface, message display) + +### Mobile + +The [Expo](https://expo.dev/)/[React Native](https://reactnative.dev/) application (`apps/mobile`) provides a native mobile experience: + +* `src/app/pdf/**`: defines the screens for the mobile document conversation interface +* `src/components/pdf/**`: contains React Native components optimized for mobile document interaction +* **API integration**: utilizes the same Hono RPC client (`packages/api`) as the web app for consistent backend communication + +This architecture ensures that core AI processing and data handling logic is shared across platforms, while enabling optimized UI implementations tailored to each environment. diff --git a/.context/turbostarter-framework-context/sections/ai/docs/replicate.md b/.context/turbostarter-framework-context/sections/ai/docs/replicate.md new file mode 100644 index 0000000..87da17c --- /dev/null +++ b/.context/turbostarter-framework-context/sections/ai/docs/replicate.md @@ -0,0 +1,85 @@ +--- +title: Replicate +description: Setup Replicate provider and learn how to use it in the starter kit. +url: /ai/docs/replicate +--- + +# Replicate + +The [Replicate](https://replicate.com) provider unlocks access to an extensive library of open-source AI models through a streamlined cloud API, seamlessly integrated with the AI SDK. It's particularly well-known for image generation capabilities. + +![Replicate](/images/docs/ai/providers/replicate.png) + +## Setup + + + + ### Generate API Key + + Visit the [Replicate website](https://replicate.com/), create an account or sign in, then navigate to your account settings to generate your personal API token. + + + + ### Add API Key to Environment + + Add your API token to your project's `.env` file (e.g., in `apps/web`): + + ```bash title=".env" + REPLICATE_API_TOKEN=your-api-key + ``` + + + + ### Configure Provider (Optional) + + The starter kit automatically uses the `REPLICATE_API_TOKEN` environment variable. For advanced configurations (such as proxies or custom headers), you can create a tailored provider instance. For comprehensive details, refer to the [AI SDK Replicate documentation](https://sdk.vercel.ai/providers/ai-sdk-providers/replicate#provider-instance). + + + +## Features + + + + Gain instant access to a diverse ecosystem of community-contributed models + spanning text generation, image creation, audio processing, video synthesis, + and numerous other AI capabilities. + + + + Create stunning visuals using various state-of-the-art open-source models + directly through the AI SDK's intuitive `generateImage` function, with + support for specific model versions and custom parameters. + + + + Fine-tune model behavior by passing specific parameters via + `providerOptions.replicate`, allowing precise control over generation + settings according to each model's unique capabilities. + + + +## Use Cases + + + + Create unique visuals, artwork, or variations based on text prompts using a + diverse set of image models. Check out the [Image Generation + Demo](/ai/docs/image). + + + + Utilize specialized open-source models for specific tasks that might not be + available through other major providers. + + + + Quickly experiment with different community-published models for various AI + tasks without managing infrastructure. + + + +## Links + +* [Replicate Website](https://replicate.com) +* [Replicate Documentation](https://replicate.com/docs) +* [AI SDK - Replicate Provider Docs](https://sdk.vercel.ai/providers/ai-sdk-providers/replicate) diff --git a/.context/turbostarter-framework-context/sections/ai/docs/security.md b/.context/turbostarter-framework-context/sections/ai/docs/security.md new file mode 100644 index 0000000..67d6c57 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/ai/docs/security.md @@ -0,0 +1,57 @@ +--- +title: Security +description: Learn about the security measures implemented in TurboStarter AI. +url: /ai/docs/security +--- + +# Security + + + Remember to regularly review your security implementations and update them as needed. + + +The starter kit incorporates several security measures to protect your application and users when interacting with AI services. + +## Authenticated endpoints + +All AI operation endpoints require user authentication. This is enforced through middleware that verifies the user's session before granting access to any AI features. + + + +The system creates anonymous sessions by default, but you can implement stronger authentication using the core framework's capabilities or the dedicated [authentication setup](/docs/web/auth/overview). + +## Credit-based access + +To prevent AI resource abuse, TurboStarter AI includes a credit-based system. Users receive a limited number of credits that are consumed when using AI features. + + + +This approach avoids misuse while enabling potential monetization. Learn about the implementation details in the [Core billing documentation](/docs/web/billing/overview). + +## Rate limiting + +API endpoints are guarded by rate limiting to prevent abuse and ensure fair usage. This protects your application from potential denial-of-service attacks and excessive request volumes. + + + +We use [`hono-rate-limiter`](https://github.com/rhinobase/hono-rate-limiter), which supports various storage options including [Redis](https://redis.io/), [Cloudflare KV](https://developers.cloudflare.com/workers/runtime-apis/kv/), and [Memcached](https://memcached.org/) for distributed rate limiting. + +## Secure API key handling + +Sensitive API keys for AI providers ([OpenAI](/ai/docs/openai), [Anthropic](/ai/docs/anthropic), [Google AI](/ai/docs/google), etc.) are managed exclusively on the backend. + +They are **NEVER** exposed to client-side code, dramatically reducing the risk of key leakage or unauthorized usage. + +## AI service abuse protection + +While TurboStarter AI provides application-level safeguards like credit limits and rate limiting, it's essential to implement additional protection directly with your AI providers. + + + Always configure spending limits, usage quotas, and monitoring alerts in your + AI provider dashboards (e.g., [OpenAI](/ai/docs/openai), + [Anthropic](/ai/docs/anthropic), [Google AI](/ai/docs/google)). These serve as + critical safety nets against unexpected costs or potential abuse that might + bypass your application-level controls. + + +By combining application-level security with provider-level controls, you'll build truly robust and secure AI applications. diff --git a/.context/turbostarter-framework-context/sections/ai/docs/stack.md b/.context/turbostarter-framework-context/sections/ai/docs/stack.md new file mode 100644 index 0000000..6a30e94 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/ai/docs/stack.md @@ -0,0 +1,77 @@ +--- +title: Tech stack +description: Learn which tools and libraries power TurboStarter AI. +url: /ai/docs/stack +--- + +# Tech stack + +## Turborepo + +[Turborepo](https://turbo.build/) is a high-performance monorepo tool that optimizes dependency management and script execution across your project. We chose this monorepo setup to simplify feature management and enable seamless code sharing between packages. + +} /> + +## Next.js + +[Next.js](https://nextjs.org) is a powerful [React](https://react.dev) framework that delivers server-side rendering, static site generation, and more. We selected Next.js for its exceptional flexibility and developer experience. It also serves as the foundation for our serverless API. + + + } /> + + } /> + + +## React Native + Expo + +[React Native](https://reactnative.dev/) is a leading open-source framework created by Facebook that enables building native mobile applications using [React](https://react.dev). It provides access to native platform capabilities while maintaining the development efficiency of React. + +[Expo](https://expo.dev/) extends React Native with a comprehensive toolkit that streamlines development, building, and deployment of iOS, Android, and web apps from a single codebase. + + + } /> + + } /> + + +## AI SDK + +[Vercel AI SDK](https://sdk.vercel.ai/) provides a robust toolkit for building AI-powered applications. It offers essential utilities and components for integrating advanced AI features, including streaming responses, interactive chat interfaces, and more. + +} /> + +## LangChain + +[LangChain](https://js.langchain.com/) is a sophisticated framework designed for language model-powered applications. It delivers critical abstractions and tools for building complex AI systems, including prompt management, memory systems, and agent architectures. + +} /> + +## Hono + +[Hono](https://hono.dev) is an ultrafast, lightweight web framework optimized for edge computing. It includes a type-safe RPC client for secure function calls from the frontend. We leverage Hono to create efficient serverless API endpoints. + +} /> + +## Tailwind CSS + +[Tailwind CSS](https://tailwindcss.com) is a utility-first CSS framework that accelerates UI development without writing custom CSS. We complement it with [Radix UI](https://radix-ui.com), a collection of accessible headless components, and [shadcn/ui](https://ui.shadcn.com), which lets you generate beautifully designed components with a single command. + + + } /> + + } /> + + } /> + + +## Drizzle + +[Drizzle](https://orm.drizzle.team/) is a type-safe, high-performance [ORM](https://orm.drizzle.team/docs/overview) (Object-Relational Mapping) for modern database management. It generates TypeScript types from your schema and enables fully type-safe queries. + +We use [PostgreSQL](https://www.postgresql.org) as our default database, but Drizzle's flexibility allows you to easily switch to MySQL, SQLite, or any [other supported database](https://orm.drizzle.team/docs/connect-overview) by updating a few configuration lines. + + + } /> + + } /> + diff --git a/.context/turbostarter-framework-context/sections/ai/docs/storage.md b/.context/turbostarter-framework-context/sections/ai/docs/storage.md new file mode 100644 index 0000000..50984de --- /dev/null +++ b/.context/turbostarter-framework-context/sections/ai/docs/storage.md @@ -0,0 +1,34 @@ +--- +title: Storage +description: Explore cloud storage services for AI applications. +url: /ai/docs/storage +--- + +# Storage + +Blob storage in TurboStarter AI offers a scalable solution for handling the diverse file types essential to modern AI applications. It works seamlessly with S3-compatible services including [AWS S3](https://aws.amazon.com/s3/), [Cloudflare R2](https://www.cloudflare.com/products/r2/), and [MinIO](https://min.io/). + +## Use cases + +Blob storage powers several key AI functions: + +* **Managing user uploads:** safely storing files like PDFs or images that users upload for AI processing, as seen in the ["Chat with PDF" demo](/ai/docs/pdf) and image analysis features +* **Preserving AI-generated content:** storing outputs from AI models, such as images from the [Image Generation demo](/ai/docs/image) or audio files from the [Text-to-Speech demo](/ai/docs/tts) +* **Powering RAG systems:** housing documents and files that serve as knowledge sources for Retrieval-Augmented Generation, used in demos like [Chat with PDF](/ai/docs/pdf) and intelligent [Agents](/ai/docs/agents) + +## Security + +Properly configuring bucket permissions for your storage provider is critical. Always restrict access based on the principle of least privilege: + +* Buckets containing user uploads or sensitive RAG documents should typically **not** be publicly accessible +* Set precise permissions that allow your application server (API) to read/write as needed while blocking unauthorized access + +Refer to your provider's documentation ([AWS S3](https://docs.aws.amazon.com/AmazonS3/latest/userguide/security-best-practices.html), [Cloudflare R2](https://developers.cloudflare.com/security-center/security-insights/roles-and-permissions/), [MinIO](https://min.io/docs/minio/linux/administration/identity-access-management/policy-based-access-control.html)) for specific guidance on securing your storage buckets. + +## Storage documentation + +For detailed setup instructions, configuration options for different storage providers, and implementation best practices, check out the core storage documentation: + + + +In summary, blob storage is essential for building sophisticated AI applications - enabling you to handle user uploads, store AI-generated files, and manage RAG document collections. diff --git a/.context/turbostarter-framework-context/sections/ai/docs/tts.md b/.context/turbostarter-framework-context/sections/ai/docs/tts.md new file mode 100644 index 0000000..8cfad64 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/ai/docs/tts.md @@ -0,0 +1,89 @@ +--- +title: Text to Speech +description: Convert text into natural-sounding speech using advanced AI voice synthesis models. +url: /ai/docs/tts +--- + +# Text to Speech + +The [Text to Speech (TTS)](https://ai.turbostarter.dev/tts) demo application transforms written text into high-quality spoken audio. It leverages state-of-the-art AI models to generate lifelike voices in various languages and styles. + + + +## Features + +Discover the powerful capabilities of this AI-powered voice synthesis solution: + + + + Access a wide range of voices from providers like [Eleven + Labs](https://elevenlabs.io/), including different accents, ages, and + emotional tones, to find the perfect match for your content. + + + + Experience near-instantaneous audio generation with streaming delivery, + providing immediate feedback as your content comes to life. + + + + Enjoy a full-featured playback interface with precise controls for playback + speed and convenient options to download generated audio files. + + + + Fine-tune your audio output with adjustable parameters for pitch, speed, and + pauses, creating the most natural and engaging delivery possible (available + options vary by provider). + + + + Benefit from a thoughtfully designed interface that makes transforming text + to speech effortless and efficient, even for first-time users. + + + +## AI models + +This application primarily utilizes specialized text-to-speech models from [Eleven Labs](https://elevenlabs.io/). + + + } /> + + +For comprehensive information about available voices and advanced customization techniques, consult the [ElevenLabs SDK documentation](https://elevenlabs.io/docs/overview). + +## Structure + +The Text-to-Speech feature is organized across the monorepo for maximum flexibility and maintainability: + +### Core + +The `@turbostarter/ai` package (`packages/ai`) contains the essential logic under `modules/tts`: + +* Comprehensive types, validation schemas, and constants specific to TTS functionality +* Core API logic for processing text-to-speech requests and interfacing with AI models +* Robust handling of generated audio file uploads to cloud storage + +### API + +The `packages/api` package defines the backend API endpoints using [Hono](https://hono.dev/): + +* `src/modules/ai/tts/tts.router.ts`: implements Hono RPC routes for TTS generation, handles input validation, applies critical middleware (authentication, credit management), and invokes the core functionality from `@turbostarter/ai`. + +### Web + +The [Next.js](https://nextjs.org/) application (`apps/web`) provides the user interface: + +* `src/app/[locale]/(apps)/tts/**`: contains the Next.js App Router pages and layouts for the TTS experience +* `src/components/tts/**`: houses reusable React components specific to the TTS interface (text input area, voice selector, audio player, etc.) + +### Mobile + +The [Expo](https://expo.dev/)/[React Native](https://reactnative.dev/) application (`apps/mobile`) provides the native mobile experience: + +* `src/app/tts/**`: defines the screens for the mobile TTS interface +* `src/components/tts/**`: contains React Native components optimized for the mobile experience +* **API interaction**: utilizes the same Hono RPC client (`packages/api`) as the web app for consistent communication with the backend + +This architecture ensures perfect consistency between platforms while allowing for optimized UI implementations tailored to each environment. diff --git a/.context/turbostarter-framework-context/sections/ai/docs/ui.md b/.context/turbostarter-framework-context/sections/ai/docs/ui.md new file mode 100644 index 0000000..3529a4c --- /dev/null +++ b/.context/turbostarter-framework-context/sections/ai/docs/ui.md @@ -0,0 +1,85 @@ +--- +title: UI +description: Learn more about UI components and design system in AI starter kit. +url: /ai/docs/ui +--- + +# UI + +TurboStarter AI builds on the core TurboStarter UI foundation to create engaging interfaces for all AI features. + +The UI architecture uses shared components and styles with platform-specific implementations: + +* **`@turbostarter/ui`**: includes shared assets, themes, and fundamental styles +* **`@turbostarter/ui-web`**: contains web components built with [Tailwind CSS](https://tailwindcss.com), [Radix UI](https://www.radix-ui.com/), and [shadcn/ui](https://ui.shadcn.com) +* **`@turbostarter/ui-mobile`**: delivers mobile components using [Uniwind](https://uniwind.dev/) and [react-native-reusables](https://reactnativereusables.com/) + +This approach maximizes code reuse while optimizing for each platform's unique capabilities. + +## UI in AI applications + +The AI starter kit leverages this foundation to create intuitive interfaces for various features and demo apps: + + + + Components for displaying conversations, user input, and streaming responses + (used in [Chatbot](/ai/docs/chat) and [Chat with PDF](/ai/docs/pdf) demos). + + + + Displaying AI-generated images as masonry grids with options for interaction + (used in [Image Generation](/ai/docs/image) demo). + + + + Structured forms for configuring AI tasks (e.g., selecting models, adjusting + parameters, modifying prompts). + + + + Visual feedback during AI processing, such as loading spinners or progress + indicators (e.g. [Text to Speech](/ai/docs/tts) voice avatar animation). + + + + UI elements for users to rate or provide feedback on AI outputs. This can + include thumbs up/down buttons or text input fields for comments. + + + + Components for displaying error messages or alerts when AI tasks fail or + encounter issues. + + + + Ensuring that all UI components are usable for individuals with + disabilities, including keyboard navigation and screen reader support. + + + + Components for displaying data or model outputs visually, such as charts, + graphs, or progress bars. + + + +## Generative UI + +A standout aspect of AI applications is their ability to dynamically create or modify UI elements based on AI responses. TurboStarter AI enables this through: + +* **AI SDK components**: libraries like the [AI SDK](https://sdk.vercel.ai/docs/introduction) provide specialized components and hooks (like `useActions` and `useUIState`) designed to render UI based on AI actions or structured data. This creates interactive elements—buttons, forms, or visualizations—that appear dynamically within conversations or workflows. +* **Structured output**: AI models can return data in specific formats (such as JSON) that your frontend parses to render appropriate components, display information, or trigger actions. For example, an AI might return product details that automatically render as interactive cards. +* **Conditional rendering**: the platform uses standard React patterns for showing, hiding, or transforming UI components based on AI interaction states. This creates smooth transitions between loading states, results displays, and follow-up options tailored to AI suggestions. + +This approach delivers truly responsive user experiences where interfaces adapt intelligently to ongoing AI processes. The [Chat demo app](/ai/docs/chat) showcases these generative UI capabilities in action. + +## Customization and further details + +Customizing appearance (themes, styling) or adding new UI components follows the same process as core TurboStarter applications. For complete guides on styling, theme management, and component development, see our core documentation: + + + + + + + +By leveraging the core UI system, TurboStarter AI ensures consistent user experiences across platforms while letting you focus on creating unique AI functionalities. diff --git a/.context/turbostarter-framework-context/sections/ai/docs/xai.md b/.context/turbostarter-framework-context/sections/ai/docs/xai.md new file mode 100644 index 0000000..4ca43dd --- /dev/null +++ b/.context/turbostarter-framework-context/sections/ai/docs/xai.md @@ -0,0 +1,90 @@ +--- +title: xAI Grok +description: Setup xAI provider and learn how to use it in the starter kit. +url: /ai/docs/xai +--- + +# xAI Grok + +The [xAI](https://x.ai) provider integrates Grok models into your application using the AI SDK. + +![xAI Grok](/images/docs/ai/providers/xai.webp) + +## Setup + + + + ### Generate API Key + + Visit the [xAI website](https://x.ai) to create an account. After signing in, navigate to your account settings to generate an API key. + + + + ### Add API Key to Environment + + Once you've acquired an API key, add it to your project's `.env` file (e.g., in `apps/web`): + + ```bash title=".env" + XAI_API_KEY=your-api-key + ``` + + + + ### Configure Provider (Optional) + + The starter kit automatically uses the `XAI_API_KEY` environment variable. For advanced configurations and customization options, refer to the comprehensive [AI SDK xAI documentation](https://sdk.vercel.ai/providers/ai-sdk-providers/xai#provider-instance). + + + +## Features + + + + Utilize xAI's language models for conversational AI, text generation, and + other natural language processing tasks. + + + + Enable language models to interact with external tools and functions, + allowing for more complex and automated task execution. + + + + Generate images based on textual descriptions using xAI's models. + + + +## Use Cases + + + + Create intelligent chatbots that engage users in natural, informative + conversations powered by xAI's Grok models, delivering responsive and + contextually relevant interactions. Experience this capability in our [Chat + Demo](/ai/docs/chat). + + + + Produce diverse, high-quality text content across various formats and + styles, harnessing the unique characteristics and capabilities of Grok + models for creative and informational outputs. + + + + Streamline operations by connecting xAI's language models with your existing + tools through function calling, enabling sophisticated automation of complex + business processes and repetitive tasks. + + + + Design striking visuals and artwork directly from text descriptions using + xAI's image generation capabilities, enabling creative applications and rich + visual content. Explore our [Image Generation Demo](/ai/docs/image) to see + these features in action. + + + +## Links + +* [xAI Website](https://x.ai) +* [AI SDK - xAI Provider Docs](https://sdk.vercel.ai/providers/ai-sdk-providers/xai) diff --git a/.context/turbostarter-framework-context/sections/extension/ai.md b/.context/turbostarter-framework-context/sections/extension/ai.md new file mode 100644 index 0000000..490d20e --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/ai.md @@ -0,0 +1,84 @@ +--- +title: AI +description: Leverage AI in your TurboStarter extension. +url: /docs/extension/ai +--- + +# AI + +When it comes to AI within the browser extension, we can differentiate two approaches: + +* **Server + client**: Traditional implementation, same as for [web](/docs/web/ai/overview) and [mobile](/docs/mobile/ai), used to stream responses generated on the server to the client. +* **Chrome built-in AI**: An [experimental implementation](https://developer.chrome.com/docs/ai/built-in) of [Gemini Nano](https://blog.google/technology/ai/google-gemini-ai/#performance) that's built into new versions of the Google Chrome browser. + +We recommend relying more on the traditional server + client approach, as it's more versatile and easier to implement. Chrome's built-in AI is a nice feature, but it's still experimental and has some limitations. + +Of course, you can always implement a *hybrid* approach which combines both solutions to achieve the best results. + +## Server + client + +The traditional usage of AI integration in the browser extension is the same as for [web app](/docs/web/ai/configuration#client-side) and [mobile app](/docs/mobile/ai). We use the exact same [API endpoint](/docs/web/ai/configuration#api-endpoint), and we leverage streaming to display answers incrementally to the user as they're generated. + +```tsx title="main.tsx" +import { useChat } from "@ai-sdk/react"; +import { DefaultChatTransport } from "ai"; + +const Popup = () => { + const { messages } = useChat({ + transport: new DefaultChatTransport({ + api: "/api/ai/chat", + }), + }); + + return ( +
+ {messages.map((message) => ( +
+ {message.parts.map((part, i) => { + switch (part.type) { + case "text": + return
{part.text}
; + } + })} +
+ ))} +
+ ); +}; + +export default Popup; +``` + +It's the most reliable and recommended way to use AI in the browser extension. Feel free to reuse or modify it to suit your specific needs. + +## Chrome built-in AI + + + Chrome's implementation of [built-in AI with Gemini Nano](https://developer.chrome.com/docs/ai/built-in) is experimental and will change as they test and address feedback. + + +Chrome's built-in AI is a preview feature. To use it, you need Chrome version 127 or greater and you must enable these flags: + +* [chrome://flags/#prompt-api-for-gemini-nano](chrome://flags/#prompt-api-for-gemini-nano): `Enabled` +* [chrome://flags/#optimization-guide-on-device-model](chrome://flags/#optimization-guide-on-device-model): `Enabled BypassPrefRequirement` +* [chrome://components/](chrome://components/): Click `Optimization Guide On Device Model` to download the model. + +Once enabled, you'll be able to use `window.ai` to access the built-in AI and do things like this: + +![Chrome built-in AI](/images/docs/extension/ai.gif) + +You can even use a [dedicated provider](https://sdk.vercel.ai/providers/community-providers/chrome-ai) from the Vercel AI SDK ecosystem to simplify its usage. Please remember that this API is still in its early stages and might change in the future. + + + The best thing is that you can use this API in every part of your extension, e.g., popup, background service worker, etc. + + It's completely safe to use on the client-side, as we're not exposing any sensitive data to the user (such as the API key in the traditional server + client approach). + + +To learn more, please check out the official [Chrome documentation](https://developer.chrome.com/docs/ai/built-in) and the articles listed below. + + + + + + diff --git a/.context/turbostarter-framework-context/sections/extension/analytics/configuration.md b/.context/turbostarter-framework-context/sections/extension/analytics/configuration.md new file mode 100644 index 0000000..32d81f9 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/analytics/configuration.md @@ -0,0 +1,92 @@ +--- +title: Configuration +description: Learn how to configure extension analytics in TurboStarter. +url: /docs/extension/analytics/configuration +--- + +# Configuration + +The `@turbostarter/analytics-extension` package offers a streamlined and flexible approach to tracking events in your TurboStarter extension using various analytics providers. It abstracts the complexities of different analytics services and provides a consistent interface for event tracking. + +In this section, we'll guide you through the configuration process for each supported provider. + +Note that the configuration is validated against a schema, so you'll see error messages in the console if anything is misconfigured. + +## Providers + +Below, you'll find detailed information on how to set up and use each supported provider. Choose the one that best suits your needs and follow the instructions in the respective accordion section. + + + + To use Google Analytics as your analytics provider, you need to [create a Google Analytics account](https://analytics.google.com/) and [set up a property](https://support.google.com/analytics/answer/9304153). + + Next, add a data stream in your Google Analytics account settings: + + 1. Navigate to [Google Analytics](https://analytics.google.com/). + 2. In the *Admin* section, under *Data collection and modification*, click on *Data Streams*. + 3. Click *Add stream*. + 4. Select *Web* as the platform. + 5. Enter the required details for the stream (at minimum, provide a name and website URL). + 6. Click *Create stream*. + + After creating the stream, you'll need two pieces of information: + + 1. Your [Measurement ID](https://support.google.com/analytics/answer/12270356) (it should look like `G-XXXXXXXXXX`): + + ![Google Analytics Measurement ID](/images/docs/web/analytics/google/id.png) + + 2. Your [Measurement Protocol API secret](https://support.google.com/analytics/answer/9814495): + + ![Google Analytics Measurement Protocol API secret](/images/docs/web/analytics/google/api-secret.png) + + Set these values in your `.env.local` file in the `apps/extension` directory and in your CI/CD provider secrets: + + ```dotenv + VITE_GOOGLE_ANALYTICS_MEASUREMENT_ID="your-measurement-id" + VITE_GOOGLE_ANALYTICS_SECRET="your-measurement-protocol-api-secret" + ``` + + Also, make sure to activate the Google Analytics provider as your analytics provider by updating the exports in: + + ```ts title="index.ts" + // [!code word:google-analytics] + export * from "./google-analytics"; + export * from "./google-analytics/env"; + ``` + + To customize the provider, you can find its definition in `packages/analytics/extension/src/providers/google-analytics` directory. + + For more information, please refer to the [Google Analytics documentation](https://developers.google.com/analytics). + + ![Google Analytics dashboard](/images/docs/web/analytics/google/dashboard.jpg) + + + + + PostHog is also one of pre-configured providers for [monitoring](/docs/extension/monitoring/overview) in TurboStarter. You can learn more about it [here](/docs/extension/monitoring/posthog). + + + To use PostHog as your analytics provider, you need to configure a PostHog instance. You can obtain the [Cloud](https://app.posthog.com/signup) instance by [creating an account](https://app.posthog.com/signup) or [self-host](https://posthog.com/docs/self-host) it. + + Then, create a project and, based on your [project settings](https://app.posthog.com/project/settings), fill the following environment variables in your `.env.local` file in `apps/extension` directory and your CI/CD provider secrets: + + ```dotenv + VITE_POSTHOG_KEY="your-posthog-api-key" + VITE_POSTHOG_HOST="your-posthog-instance-host" + ``` + + Also, make sure to activate the PostHog provider as your analytics provider by updating the exports in: + + ```ts title="index.ts" + // [!code word:posthog] + export * from "./posthog"; + export * from "./posthog/env"; + ``` + + To customize the provider, you can find its definition in `packages/analytics/extension/src/providers/posthog` directory. + + For more information, please refer to the [PostHog documentation](https://posthog.com/docs/advanced/browser-extension). + + ![PostHog dashboard](/images/docs/web/analytics/posthog.png) + + diff --git a/.context/turbostarter-framework-context/sections/extension/analytics/overview.md b/.context/turbostarter-framework-context/sections/extension/analytics/overview.md new file mode 100644 index 0000000..df1c8cd --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/analytics/overview.md @@ -0,0 +1,59 @@ +--- +title: Overview +description: Get started with extension analytics in TurboStarter. +url: /docs/extension/analytics/overview +--- + +# Overview + +When it comes to extension analytics, we can distinguish between two types: + +* **Store listing analytics**: Used to track the performance of your extension's store listing (e.g., how many people have viewed your extension in the store or how many have installed it). +* **In-extension analytics**: Tracks user actions within your extension (e.g., how many users triggered your popup, how many users modified extension settings, etc.). + +The `@turbostarter/analytics-extension` package provides a set of tools to easily implement both types of analytics in your extension. + +## Store listing analytics + +Interpreting your extension's store listing metrics can help you evaluate how changes to your extension and store listing affect conversion rates. For example, you can identify countries with a high number of visitors to prioritize supporting languages for those regions. + +While each store implements a different set of metrics, there are some common ones you should be aware of: + +* **Active installs**: The number of users who have installed your extension. +* **Active users**: The number of users who have used your extension. +* **Page views**: The number of times users have viewed your extension's detail page on the respective store. + +To track more detailed metrics, you can opt in to Google Analytics in the Chrome Web Store's developer dashboard. + +You can find this option under *Additional metrics* on the *Store listing* tab of your extension's control panel: + +![Chrome Web Store - Store listing - Additional metrics](/images/docs/extension/analytics/opt-in-analytics.png) + + + The Chrome Web Store manages the account for you and makes the data available + in the Google Analytics dashboard. + + +By enabling this feature, you can optimize your extension's store listing based on metrics such as bounce rate, time on page, and more. This can lead to more installs and ultimately more users for your extension. + +To learn more about the limitations of this type of analytics and how to adjust event details, please refer to the following sections in the official documentation: + + + + + + + +## In-extension analytics + +TurboStarter comes with built-in support for tracking in-extension analytics. To learn more about each supported provider and how to configure them, see their respective sections: + + + + + + + +All configuration and setup is built-in with a unified API, allowing you to switch between providers by simply changing the exports. You can even introduce your own provider without breaking any tracking-related logic. + +In the following sections, we'll cover how to set up each provider and how to track events in your extension. diff --git a/.context/turbostarter-framework-context/sections/extension/analytics/tracking.md b/.context/turbostarter-framework-context/sections/extension/analytics/tracking.md new file mode 100644 index 0000000..9fc9bae --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/analytics/tracking.md @@ -0,0 +1,80 @@ +--- +title: Tracking events +description: Learn how to track events in your TurboStarter extension. +url: /docs/extension/analytics/tracking +--- + +# Tracking events + +The strategy for tracking events that every provider has to implement is extremely simple: + +```ts +export type AllowedPropertyValues = string | number | boolean; + +type TrackFunction = ( + event: string, + data?: Record, +) => void; + +export interface AnalyticsProviderStrategy { + track: TrackFunction; +} +``` + + + You don't need to worry much about this implementation, as all the providers are already configured for you. However, it's useful to be aware of this structure if you plan to add your own custom provider. + + +As shown above, each provider must supply the `track` function. This function is responsible for sending event data to the provider. + +To track an event in any part of your extension, simply call the `track` method, passing the event name and an optional data object: + +```tsx title="main.tsx" +import { track } from "@turbostarter/analytics-extension"; + +const Popup = () => { + return ( + + ); +}; + +export default Popup; +``` + +## Identifying users + +Linking events to specific users enables you to build a full picture of how they're using your product across different sessions, devices, and platforms. + +For identification purposes, we're extending the strategy with the `identify` and `reset` methods. They are optional and only needed if you want to identify users in your app and associate their actions with a specific user ID. + +```ts +type IdentifyFunction = ( + userId: string, + traits?: Record, +) => void; + +export interface AnalyticsProviderClientStrategy { + identify: IdentifyFunction; + reset: () => void; +} +``` + +To identify users, call the `identify` method, passing the user's ID and an optional traits object: + +```tsx +import { identify } from "@turbostarter/analytics-extension"; + +identify("user-123", { name: "John Doe" }); +``` + +This will associate all future events with the user's ID, allowing you to track user behavior and gain valuable insights into your application's usage patterns. + + + The `identify` method is configured out-of-the-box to react on changes to the user's authentication state. + + When the user is authenticated, the `identify` method will be called with the user's ID and the user's traits. When the user is logged out, the `reset` method will be called to clear the existing user identification. + + +Congratulations! You've now mastered event tracking in your TurboStarter extension. With this knowledge, you're well-equipped to analyze user behaviors and gain valuable insights into your extension's usage patterns. Happy analyzing! 📊 diff --git a/.context/turbostarter-framework-context/sections/extension/api/client.md b/.context/turbostarter-framework-context/sections/extension/api/client.md new file mode 100644 index 0000000..df707fe --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/api/client.md @@ -0,0 +1,197 @@ +--- +title: Using API client +description: How to use API client to interact with the API. +url: /docs/extension/api/client +--- + +# Using API client + +In browser extension code, you can only access the API client from the **client-side.** + +When you create a new component or piece of your extension and want to fetch some data, you can use the API client to do so. + +## Creating a client + +We're creating a client-side API client in `apps/extension/src/lib/api/index.tsx` file. It's a simple wrapper around the [@tanstack/react-query](https://tanstack.com/query/latest/docs/framework/react/overview) that fetches or mutates data from the API. + +It also requires wrapping your views in a `QueryClientProvider` component to provide the API client to the rest of the components. + +We recommend to create a separate layout file, which will be used to wrap your pages. TurboStarter comes with a `layout.tsx` file in the `modules/common/layout` folder, which you can use as a template: + +```tsx title="layout.tsx" +export const Layout = ({ + children, + loadingFallback, + errorFallback, +}: LayoutProps) => { + return ( + + + {children} + + + ); +}; +``` + +Remember that every part of your extension will be mounted as a **separate** React component, so you need to wrap each of them in the `QueryClientProvider` component if you want to use the API client inside: + +```tsx title="app/popup/main.tsx" +import { Layout } from "~/modules/common/layout/layout"; + +export default function Popup() { + return {/* your popup code here */}; +} +``` + + + Inside the `apps/extension/src/lib/api/index.tsx` we're calling a function to get base url of your api, so make sure it's set correctly (especially on production) and your web api endpoint is corresponding with the name there. + + ```tsx title="index.tsx" + const getBaseUrl = () => { + return env.VITE_SITE_URL; + }; + ``` + + As you can see we're mostly relying on the [environment variables](/docs/extension/configuration/environment-variables) to get it, so there shouldn't be any issues with it, but in case, please be aware where to find it 😉 + + +## Queries + +Of course, everything comes already configured for you, so you just need to start using `api` in your components/screens. + +For example, to fetch the list of posts you can use the `useQuery` hook: + +```tsx title="posts.tsx" +import { api } from "~/lib/api"; + +export const Posts = () => { + const { data: posts, isLoading } = useQuery({ + queryKey: ["posts"], + queryFn: async () => { + const response = await api.posts.$get(); + + if (!response.ok) { + throw new Error("Failed to fetch posts!"); + } + + return response.json(); + }, + }); + + if (isLoading) { + return

Loading...

; + } + + /* do something with the data... */ + return ( +
+

{JSON.stringify(posts)}

+
+ ); +}; +``` + +It's using the `@tanstack/react-query` [useQuery API](https://tanstack.com/query/latest/docs/framework/react/reference/useQuery), so you shouldn't have any troubles with it. + + + + + + + +## Mutations + +If you want to perform a mutation in your extension code, you can use the `useMutation` hook that comes straight from the integration with [Tanstack Query](https://tanstack.com/query): + +```tsx title="modules/popup/form.tsx" +import { api } from "~/lib/api"; + +export const CreatePost = () => { + const queryClient = useQueryClient(); + const { mutate } = useMutation({ + mutationFn: async (post: PostInput) => { + const response = await api.posts.$post(post); + + if (!response.ok) { + throw new Error("Failed to create post!"); + }, + }, + onSuccess: () => { + toast.success("Post created successfully!"); + queryClient.invalidateQueries({ queryKey: ["posts"] }); + }, + }); + + return ; +}; +``` + +Here, we're also invalidating the query after the mutation is successful. This is a very important step to make sure that the data is updated in the UI. + + + + + + + +## Handling responses + +As you can see in the examples above, the [Hono RPC](https://hono.dev/docs/guides/rpc) client returns a plain `Response` object, which you can use to get the data or handle errors. However, implementing this handling in every query or mutation can be tedious and will introduce unnecessary boilerplate in your codebase. + +That's why we've developed the `handle` function that unwraps the response for you, handles errors, and returns the data in a consistent format. You can safely use it with any procedure from the API client: + + + + ```tsx + // [!code word:handle] + import { handle } from "@turbostarter/api/utils"; + + import { api } from "~/lib/api"; + + export const Posts = () => { + const { data: posts, isLoading } = useQuery({ + queryKey: ["posts"], + queryFn: handle(api.posts.$get), + }); + + if (isLoading) { + return

Loading...

; + } + + /* do something with the data... */ + return ( +
+

{JSON.stringify(posts)}

+
+ ); + }; + ``` +
+ + + ```tsx + // [!code word:handle] + import { handle } from "@turbostarter/api/utils"; + + import { api } from "~/lib/api/client"; + + export const CreatePost = () => { + const queryClient = useQueryClient(); + const { mutate } = useMutation({ + mutationFn: handle(api.posts.$post), + onSuccess: () => { + toast.success("Post created successfully!"); + queryClient.invalidateQueries({ queryKey: ["posts"] }); + }, + }); + + return ; + }; + ``` + +
+ +With this approach, you can focus on the business logic instead of repeatedly writing code to handle API responses in your browser extension components, making your extension's codebase more readable and maintainable. + +The same error handling and response unwrapping benefits apply whether you're building web, mobile, or extension interfaces - allowing you to keep your data fetching logic consistent across all platforms. diff --git a/.context/turbostarter-framework-context/sections/extension/api/overview.md b/.context/turbostarter-framework-context/sections/extension/api/overview.md new file mode 100644 index 0000000..11a6c16 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/api/overview.md @@ -0,0 +1,45 @@ +--- +title: Overview +description: Get started with the API. +url: /docs/extension/api/overview +--- + +# Overview + + + To enable communication between your WXT extension and the server in a production environment, the web application with Hono API must be deployed first. + + + + + + + + +TurboStarter is designed to be a scalable and production-ready full-stack starter kit. One of its core features is a dedicated and extensible API layer. To enable this in a type-safe manner, we chose [Hono](https://hono.dev) as the API server and client library. + + + Hono is a small, simple, and ultrafast web framework that gives you a way to + define your API endpoints with full type safety. It provides built-in + middleware for common needs like validation, caching, and CORS. It also + includes an [RPC client](https://hono.dev/docs/guides/rpc) for making + type-safe function calls from the frontend. Being edge-first, it's optimized + for serverless environments and offers excellent performance. + + +All API endpoints and their resolvers are defined in the `packages/api/` package. Here you will find a `modules` folder that contains the different feature modules of the API. Each module has its own folder and exports all its resolvers. + +For each module, we create a separate Hono route in the `packages/api/index.ts` file and aggregate all sub-routers into one main router. + +The API is then exposed as a route handler that will be provided as a Next.js API route: + +```ts title="apps/web/src/app/api/[...route]/route.ts" +import { handle } from "hono/vercel"; + +import { appRouter } from "@turbostarter/api"; + +const handler = handle(appRouter); +export { handler as GET, handler as POST }; +``` + +Learn more about how to use the API in your browser extension code in the following sections: diff --git a/.context/turbostarter-framework-context/sections/extension/auth/overview.md b/.context/turbostarter-framework-context/sections/extension/auth/overview.md new file mode 100644 index 0000000..3937bc7 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/auth/overview.md @@ -0,0 +1,46 @@ +--- +title: Overview +description: Learn how to authenticate users in your extension. +url: /docs/extension/auth/overview +--- + +# Overview + +TurboStarter uses [Better Auth](https://better-auth.com) to handle authentication. It's a secure, production-ready authentication solution that integrates seamlessly with many frameworks and provides enterprise-grade security out of the box. + + + One of the core principles of TurboStarter is to do things **as simple as possible**, and to make everything **as performant as possible**. + + Better Auth provides an excellent developer experience with minimal configuration required, while maintaining enterprise-grade security standards. Its framework-agnostic approach and focus on performance makes it the perfect choice for TurboStarter. + + Recently, Better Auth [announced](https://www.better-auth.com/blog/authjs-joins-better-auth) an incorporation of [Auth.js (27k+ stars on Github)](https://authjs.dev/), making it even more powerful and flexible. + + +![Better Auth](/images/docs/better-auth.png) + +You can read more about Better Auth in the [official documentation](https://better-auth.com/docs). + + + To keep things simple and secure, **the extension shares the same authentication session with your web app.** + + This is a common approach used by popular services like [Notion](https://www.notion.so) and [Google Workspace](https://workspace.google.com/). The benefits include: + + * Users only need to sign in once through the web app + * The extension automatically inherits the authenticated session + * Sign out actions are synchronized across platforms + * Reduced security surface area and complexity + + +Before setting up extension authentication, make sure to first [configure authentication for your web app](/docs/web/auth/overview) and then head back to the extension code. + +The following sections cover everything you need to know about authentication in your extension: + + + + + + + + + + diff --git a/.context/turbostarter-framework-context/sections/extension/auth/session.md b/.context/turbostarter-framework-context/sections/extension/auth/session.md new file mode 100644 index 0000000..ad3a09b --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/auth/session.md @@ -0,0 +1,123 @@ +--- +title: Session +description: Learn how to manage the user session in your extension. +url: /docs/extension/auth/session +--- + +# Session + +We're not implementing fully-featured auth flow in the extension. Instead, **we're sharing the same auth session with the web app.** + +It's a common practice in the industry used e.g. by [Notion](https://www.notion.so) and [Google Workspace](https://workspace.google.com/). + +That way, when the user is signed in to the web app, the extension can use the same session to authenticate the user, so he doesn't have to sign in again. Also signing out from the extension will affect both platforms. + + + For browser extensions, we need to define an [authentication trusted origin](https://www.better-auth.com/docs/reference/security#trusted-origins) using an extension scheme. + + Extension schemes (like `chrome-extension://...`) are used for redirecting users to specific screens after authentication and sharing the auth session with the web app. + + To find your extension ID, open Chrome and go to `chrome://extensions/`, enable Developer Mode in the top right, and look for your extension's ID. Then add it to your auth server configuration: + + ```ts title="server.ts" + export const auth = betterAuth({ + ... + + trustedOrigins: ["chrome-extension://your-extension-id"], + + ... + }); + ``` + + Adding your extension scheme to the trusted origins list is crucial for security - it prevents CSRF attacks and blocks malicious open redirects by ensuring only requests from approved origins (your extension) are allowed through. + + [Read more about auth security in Better Auth's documentation.](https://www.better-auth.com/docs/reference/security) + + +## Cookies + +When the user signs in to the [web app](/docs/web) through our [Better Auth API](/docs/web/auth/configuration#api), web app is setting the cookie with the session token under your app's domain, which is later used to validate the session on the server. + +You can find your cookie in *Cookies* tab in the browser's developer tools (remember to be logged in to the app to check it): + +![Session cookie](/images/docs/extension/auth/cookie.png) + +To enable your extension to read the cookie and that way share the session with the web app, you need to set the `cookies` permission in the `wxt.config.ts` under `manifest.permissions` field: + +```ts title="wxt.config.ts" +export default defineConfig({ + manifest: { + permissions: ["cookies"], + }, +}); +``` + +And to be able to read the cookie from your app url, you need to set `host_permissions`, which will include your app url: + +```ts title="wxt.config.ts" +export default defineConfig({ + manifest: { + host_permissions: ["http://localhost/*", "https://your-app-url.com/*"], + }, +}); +``` + +Then you would be able to share the cookie with API requests and also read its value using `browser.cookies` API. + + + Avoid using `` in `host_permissions`. It affects all urls and may cause security issues, as well as a [rejection](https://developer.chrome.com/docs/webstore/review-process#review-time-factors) from the destination store. + + + + + + + + +## Reading session + +You **don't** need to worry about reading, parsing, or validating the session cookie. TurboStarter comes with a pre-built solution that ensures your session is correctly shared with the web app. + +It also ensures that appropriate cookies are passed to [API](/docs/web/api/overview) requests, so you can safely use [protected endpoints](/docs/web/api/protected-routes) (that require authentication) in your extension. + +To get session details in your extension code (e.g., inside a popup window), you can leverage the `useSession` hook provided by the [auth client](https://www.better-auth.com/docs/basic-usage#client-side) (which is also used in the web and mobile apps): + +```tsx title="user.tsx" +import { authClient } from "~/lib/auth"; + +const User = () => { + const { + data: { user, session }, + isPending, + } = authClient.useSession(); + + if (isPending) { + return

Loading...

; + } + + /* do something with the session data... */ + return

{user?.email}

; +}; +``` + +That's how you can access user details right in your extension. + +## Signing out + +Signing out from the extension also involves using the well-known `signOut` function that is derived from our [auth client](https://www.better-auth.com/docs/basic-usage#signout): + +```tsx title="logout.tsx" +import { authClient } from "~/lib/auth"; + +export const Logout = () => { + return ; +}; +``` + +The session is automatically invalidated, so the next use of `useSession` or any other query that depends on the session will return `null`. The UI for both the extension and the web app will be updated to show the user as logged out. + + + As web app is using the same session cookie, the user will be signed out from the web app as well. **This is intentional**, as your extension will most probably serves as an add-on for the web app and it doesn't make sense to keep the user signed in there if the extension is not used. + + +![Sign out](/images/docs/web/auth/sign-out.png) diff --git a/.context/turbostarter-framework-context/sections/extension/billing.md b/.context/turbostarter-framework-context/sections/extension/billing.md new file mode 100644 index 0000000..ee839e9 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/billing.md @@ -0,0 +1,68 @@ +--- +title: Billing +description: Get started with billing in TurboStarter. +url: /docs/extension/billing +--- + +# Billing + +As you could guess, there is no sense in implementing the whole billing process inside the browser extension, so we're relying on the [web app](/docs/web/billing/overview) to handle it. + +> You probably won't display pricing tables inside a popup window, right? + +You can customize the whole flow and onboarding process when a user purchases a plan in your [web app](/docs/web/billing/overview). + +Then you would be able to easily fetch customer data to ensure that the user has access to correct extension features. + +## Fetching customer data + +When your user has purchased a plan from your landing page or web app, you can easily fetch their data using the [API](/docs/extension/api/client). + +To do so, just invoke the `getCustomer` query on the `billing` router: + +```tsx title="customer-screen.tsx" +import { api } from "~/lib/api"; + +export default function CustomerScreen() { + const { data: customer, isLoading } = useQuery({ + queryKey: ["customer"], + queryFn: handle(api.billing.customer.$get), + }); + + if (isLoading) return

Loading...

; + + return

{customer?.plan}

; +} +``` + +You may also want to ensure that user is logged in before fetching their billing data to avoid unnecessary API calls. + +```tsx title="header.tsx" +import { api } from "~/lib/api"; +import { authClient } from "~/lib/auth"; + +export const User = () => { + const { + data: { user }, + } = authClient.useSession(); + + const { data: customer } = useQuery({ + queryKey: ["customer"], + queryFn: handle(api.billing.customer.$get), + enabled: !!user, // [!code highlight] + }); + + if (!user || !customer) { + return null; + } + + return ( +
+

{user.email}

+

{customer.plan}

+
+ ); +}; +``` + +Read more about [auth in extension](/docs/extension/auth/overview). diff --git a/.context/turbostarter-framework-context/sections/extension/cli.md b/.context/turbostarter-framework-context/sections/extension/cli.md new file mode 100644 index 0000000..5c98bcd --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/cli.md @@ -0,0 +1,92 @@ +--- +title: CLI +description: Start your new app project with a single command. +url: /docs/extension/cli +--- + +# CLI + + + +To help you get started with TurboStarter **as quickly as possible**, we've developed a [CLI](https://www.npmjs.com/package/@turbostarter/cli) that enables you to create a new project (with all the configuration) in seconds. + +The CLI is a set of commands that will help you create a new project, generate code, and manage your project efficiently. + +Currently, the following action is available: + +* **Starting a new project** - Generate starter code for your project with all necessary configurations in place (billing, database, emails, etc.) + +**The CLI is in beta**, and we're actively working on adding more commands and actions. Soon, the following features will be available: + +* **Translations** - Translate your project, verify translations, and manage them effectively +* **Installing plugins** - Easily install plugins for your project +* **Dynamic code generation** - Generate dynamic code based on your project structure + +## Installation + +You can run commands using `npx`: + +```bash +npx turbostarter +npx @turbostarter/cli@latest +``` + + + If you don't want to install the CLI globally, you can simply replace the examples below with `npx @turbostarter/cli@latest` instead of `turbostarter`. + + This also allows you to always run the latest version of the CLI without having to update it. + + +## Usage + +Running the CLI without any arguments will display the general information about the CLI: + +```bash +Usage: turbostarter [options] [command] + +Your Turbo Assistant for starting new projects, adding plugins and more. + +Options: + -v, --version display the version number + -h, --help display help for command + +Commands: + new create a new TurboStarter project + help [command] display help for command +``` + +You can also display help for it or check the actual version. + +### Starting a new project + +Use the `new` command to initialize configuration and dependencies for a new project. + +```bash +npx turbostarter new +``` + +You will be asked a few questions to configure your project: + +```bash +✔ All prerequisites are satisfied, let's start! 🚀 + +? What do you want to ship? › + ◉ Web app + ◉ Mobile app + ◯ Browser extension +? Enter your project name. › +? How do you want to use database? › + Local (powered by Docker) + Cloud +? What do you want to use for billing? › + Stripe + Lemon Squeezy + +... + +🎉 You can now get started. Open the project and just ship it! 🎉 + +Problems? https://www.turbostarter.dev/docs +``` + +It will create a new project, configure providers, install dependencies and start required services in development mode. diff --git a/.context/turbostarter-framework-context/sections/extension/configuration/app.md b/.context/turbostarter-framework-context/sections/extension/configuration/app.md new file mode 100644 index 0000000..fad39e4 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/configuration/app.md @@ -0,0 +1,63 @@ +--- +title: App configuration +description: Learn how to setup the overall settings of your extension. +url: /docs/extension/configuration/app +--- + +# App configuration + +The application configuration is set at `apps/extension/src/config/app.ts`. This configuration stores some overall variables for your application. + +This allows you to host multiple apps in the same monorepo, as every application defines its own configuration. + +The recommendation is to **not update this directly** - instead, please define the environment variables and override the default behavior. The configuration is strongly typed so you can use it safely accross your codebase - it'll be validated at build time. + +```ts title="apps/extension/src/config/app.ts" +import env from "env.config"; + +export const appConfig = { + name: env.VITE_PRODUCT_NAME, + url: env.VITE_SITE_URL, + locale: env.VITE_DEFAULT_LOCALE, + theme: { + mode: env.VITE_THEME_MODE, + color: env.VITE_THEME_COLOR, + }, +} as const; +``` + +For example, to set the extension default theme color, you'd update the following variable: + +```dotenv title=".env.local" +VITE_THEME_COLOR="yellow" +``` + + + Do NOT use `process.env` to get the values of the variables. Variables + accessed this way are not validated at build time, and thus the wrong variable + can be used in production. + + +## WXT config + +To configure framework-specific settings, you can use the `wxt.config.ts` file. You can configure a lot of options there, such as [manifest](/docs/extension/configuration/manifest), [project structure](https://wxt.dev/guide/essentials/project-structure.html) or even [underlying Vite config](https://wxt.dev/guide/essentials/config/vite.html): + +```ts title="wxt.config.ts" +import { defineConfig } from "wxt"; + +export default defineConfig({ + srcDir: "src", + entrypointsDir: "app", + outDir: "build", + modules: [], + manifest: { + // Put manifest changes here + }, + vite: () => ({ + // Override config here, same as `defineConfig({ ... })` + // inside vite.config.ts files + }), +}); +``` + +Make sure to setup it correctly, as it's the main source of config for your development, build and publishing process. diff --git a/.context/turbostarter-framework-context/sections/extension/configuration/environment-variables.md b/.context/turbostarter-framework-context/sections/extension/configuration/environment-variables.md new file mode 100644 index 0000000..587f4c0 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/configuration/environment-variables.md @@ -0,0 +1,123 @@ +--- +title: Environment variables +description: Learn how to configure environment variables. +url: /docs/extension/configuration/environment-variables +--- + +# Environment variables + +Environment variables are defined in the `.env` file in the root of the repository and in the root of the `apps/extension` package. + +* **Shared environment variables**: Defined in the **root** `.env` file. These are shared between environments (e.g., development, staging, production) and apps (e.g., web, extension). +* **Environment-specific variables**: Defined in `.env.development` and `.env.production` files. These are specific to the development and production environments. +* **App-specific variables**: Defined in the app-specific directory (e.g., `apps/extension`). These are specific to the app and are not shared between apps. +* **Bundle-specific variables**: Specific to the [bundle target](https://wxt.dev/guide/essentials/config/environment-variables.html#built-in-environment-variables) (e.g. `.env.safari`, `.env.firefox`) or [bundle tag](https://wxt.dev/guide/essentials/config/environment-variables.html#built-in-environment-variables) (e.g. `.env.testing`) +* **Build environment variables**: Not stored in the `.env` file. Instead, they are stored in the environment variables of the CI/CD system. +* **Secret keys**: They're not stored on the extension side, instead [they're defined on the web side.](/docs/web/configuration/environment-variables#secret-keys) + +## Shared variables + +Here you can add all the environment variables that are shared across all the apps. + +To override these variables in a specific environment, please add them to the specific environment file (e.g. `.env.development`, `.env.production`). + +```dotenv title=".env.local" +# Shared environment variables + +# The database URL is used to connect to your database. +DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres" + +# The name of the product. This is used in various places across the apps. +PRODUCT_NAME="TurboStarter" + +# The url of the web app. Used mostly to link between apps. +URL="http://localhost:3000" + +... +``` + +## App-specific variables + +Here you can add all the environment variables that are specific to the app (e.g. `apps/extension`). + +You can also override the shared variables defined in the root `.env` file. + +```dotenv title="apps/extension/.env.local" +# App-specific environment variables + +# Env variables extracted from shared to be exposed to the client in WXT (Vite) extension +VITE_SITE_URL="${URL}" +VITE_DEFAULT_LOCALE="${DEFAULT_LOCALE}" + +# Theme mode and color +VITE_THEME_MODE="system" +VITE_THEME_COLOR="orange" + +... +``` + + + To make environment variables available in the browser extension code, you need to prefix them with `VITE_`. They will be injected to the code during the build process. + + Only environment variables prefixed with `VITE_` will be injected. + + [Read more about Vite environment variables.](https://vite.dev/guide/env-and-mode.html#env-files) + + +## Bundle-specific variables + +WXT also provides environment variables specific to a certain [build target](https://wxt.dev/guide/essentials/config/environment-variables.html#built-in-environment-variables) or [build tag](https://wxt.dev/guide/essentials/config/environment-variables.html#built-in-environment-variables) when creating the final bundle. Given the following build command: + +```json title="package.json" +"scripts": { + "build": "wxt build -b firefox --mode testing" +} +``` + +The following env files will be considered, ordered by priority: + +* `.env.firefox` +* `.env.testing` +* `.env` + +You shouldn't worry much about this, as TurboStarter comes with already configured build processes for all the major browsers. + +## Build environment variables + +To allow your extension to build properly on CI you need to define your environment variables on your CI/CD system (e.g. [Github Actions](https://docs.github.com/en/actions/learn-github-actions/environment-variables)). + +TurboStarter comes with predefined Github Actions workflow used to build and submit your extension to the stores. It's located in `.github/workflows/publish-extension.yml` file. + +To correctly set up the build environment variables, you need to define them under `env` section and then add them as a [secrets](http://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions) to your repository. + +```yaml title="publish-extension.yml" +... + +jobs: + extension: + name: 🚀 Publish extension + runs-on: ubuntu-latest + environment: Production + env: + VITE_SITE_URL: ${{ secrets.SITE_URL }} + + ... + +``` + +We'll go through the whole process of building and publishing the extension in the [publishing guide](/docs/extension/publishing/checklist). + +## Secret keys + +Secret keys and sensitive information are to be **never** stored on the extension app code. + + + It means that you will need to add the secret keys to the **web app, where the API is deployed.** + + The browser extension should only communicate with the backend API, which is typically part of the web app. The web app is responsible for handling sensitive operations and storing secret keys securely. + + [See web documentation for more details.](/docs/web/configuration/environment-variables#secret-keys) + + This is not a TurboStarter-specific requirement, but a best practice for security for any + application. Ultimately, it's your choice. + diff --git a/.context/turbostarter-framework-context/sections/extension/configuration/manifest.md b/.context/turbostarter-framework-context/sections/extension/configuration/manifest.md new file mode 100644 index 0000000..4d08820 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/configuration/manifest.md @@ -0,0 +1,111 @@ +--- +title: Manifest +description: Learn how to configure the manifest of your extension. +url: /docs/extension/configuration/manifest +--- + +# Manifest + +As a requirement from web stores, every extension must have a `manifest.json` file in its root directory that lists important information about the structure and behavior of that extension. + +It's a JSON file that contains metadata about the extension, such as its name, version, and permissions. + +You can read more about it in the [official documentation](https://developer.chrome.com/docs/extensions/reference/manifest). + +## Where is the `manifest.json` file? + +WXT **abstracts away** the manifest file. The framework generates the manifest under the hood based on your source files and configurations you export from your code, similar to how Next.js abstracts page routing and SSG with the file system and page components. + +That way, you don't have to manually create the `manifest.json` file and worry about correctly setting all the fields. + +Most of the common properties are taken from the `package.json` and `wxt.config.ts` files: + +| Manifest Field | Abstractions | +| ------------------------ | ------------------------------------------------------------- | +| icons | Auto generated with the `icon.png` in the `/assets` directory | +| action, browser\_actions | Popup window | +| options\_ui | Options page | +| content\_scripts | Content scripts | +| background | Background service worker | +| version | set by the `version` field in `package.json` | +| name | set by the `name` field in `wxt.config.ts` | +| description | set by the `description` field in `wxt.config.ts` | +| author | set by the `author` field in `wxt.config.ts` | +| homepage\_url | set by the `homepage` field in `wxt.config.ts` | + +WXT build process centralizes common metadata and resolves any static file references (such as popup, background, content scripts, and so on) automatically. + +This enables you to focus on the metadata that matters, such as name, description, OAuth, and so on. + +## Overriding manifest + +Sometimes, you want to override the default manifest fields (e.g. because you need to add a new permission that is required for your extension to work). + +You'll need to modify your project's `wxt.config.ts` like so: + +```ts title="apps/extension/wxt.config.ts" +export default defineConfig({ + manifest: { + permissions: ["activeTab"], + }, +}); +``` + +Then, your settings will be merged with the settings auto-generated by WXT. + +### Environment variables + +You can use environment variables inside the manifest overrides: + +```ts title="apps/extension/wxt.config.ts" +export default defineConfig({ + manifest: { + browser_specific_settings: { + gecko: { + id: import.meta.env.VITE_FIREFOX_EXT_ID, + }, + }, + }, +}); +``` + +If the environment variable could not be found, the field will be removed completely from the manifest. + +### Locales + +TurboStarter extension supports [extension localization](https://developer.chrome.com/docs/extensions/reference/api/i18n) out-of-the-box. You can customize e.g. your extension's name and description based on the language of the user's browser. + +Locales are defined in the `/public/_locales` directory. The directory should contain a `messages.json` file for each language you want to support (e.g. `/public/_locales/en/messages.json` and `/public/_locales/es/messages.json`). + +By default, the first locale alphabetically available is used as default. However, you can specify a `default_locale` in your manifest like so: + +```ts title="apps/extension/wxt.config.ts" +export default defineConfig({ + manifest: { + default_locale: "en", + }, +}); +``` + +To reference a locale string inside your manifest overrides, wrap the key inside `__MSG___`: + +```ts title="apps/extension/wxt.config.ts" +export default defineConfig({ + manifest: { + name: "__MSG_extensionName__", + description: "__MSG_extensionDescription__", + }, +}); +``` + +Apart of this, we also configure [in-extension internationalization](/docs/extension/internationalization) out-of-the-box to easily translate your components and views. + + + + + + + + + + diff --git a/.context/turbostarter-framework-context/sections/extension/customization/add-app.md b/.context/turbostarter-framework-context/sections/extension/customization/add-app.md new file mode 100644 index 0000000..f735854 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/customization/add-app.md @@ -0,0 +1,83 @@ +--- +title: Adding apps +description: Learn how to add apps to your Turborepo workspace. +url: /docs/extension/customization/add-app +--- + +# Adding apps + + + This is an **advanced topic** - you should only follow these instructions if you are sure you want to add a new app to your TurboStarter project within your monorepo and want to keep pulling updates from the TurboStarter repository. + + +In some ways - creating a new repository may be the easiest way to manage your application. However, if you want to keep your application within the monorepo and pull updates from the TurboStarter repository, you can follow these instructions. + +To pull updates into a separate application outside of `extension` - we can use [git subtree](https://www.atlassian.com/git/tutorials/git-subtree). + +Basically, we will create a subtree at `apps/extension` and create a new remote branch for the subtree. When we create a new application, we will pull the subtree into the new application. This allows us to keep it in sync with the `apps/extension` folder. + +To add a new app to your TurboStarter project, you need to follow these steps: + + + + ## Create a subtree + + First, we need to create a subtree for the `apps/extension` folder. We will create a branch named `extension-branch` and create a subtree for the `apps/extension` folder. + + ```bash + git subtree split --prefix=apps/extension --branch extension-branch + ``` + + + + ## Create a new app + + Now, we can create a new application in the `apps` folder. + + Let's say we want to create a new app `ai-chat` at `apps/ai-chat` with the same structure as the `apps/extension` folder (which acts as the template for all new apps). + + ```bash + git subtree add --prefix=apps/ai-chat origin extension-branch --squash + ``` + + You should now be able to see the `apps/ai-chat` folder with the contents of the `apps/extension` folder. + + + + ## Update the app + + When you want to update the new application, follow these steps: + + ### Pull the latest updates from the TurboStarter repository + + The command below will update all the changes from the TurboStarter repository: + + ```bash + git pull upstream main + ``` + + ### Push the `extension-branch` updates + + After you have pulled the updates from the TurboStarter repository, you can split the branch again and push the updates to the extension-branch: + + ```bash + git subtree split --prefix=apps/extension --branch extension-branch + ``` + + Now, you can push the updates to the `extension-branch`: + + ```bash + git push origin extension-branch + ``` + + ### Pull the updates to the new application + + Now, you can pull the updates to the new application: + + ```bash + git subtree pull --prefix=apps/ai-chat origin extension-branch --squash + ``` + + + +That's it! You now have a new application in the monorepo 🎉 diff --git a/.context/turbostarter-framework-context/sections/extension/customization/add-package.md b/.context/turbostarter-framework-context/sections/extension/customization/add-package.md new file mode 100644 index 0000000..ee9ddd7 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/customization/add-package.md @@ -0,0 +1,100 @@ +--- +title: Adding packages +description: Learn how to add packages to your Turborepo workspace. +url: /docs/extension/customization/add-package +--- + +# Adding packages + + + This is an **advanced topic** - you should only follow these instructions if you are sure you want to add a new package to your TurboStarter application instead of adding a folder to your application in `apps/web` or modify existing packages under `packages`. You don't need to do this to add a new page or component to your application. + + +To add a new package to your TurboStarter application, you need to follow these steps: + + + + ## Generate a new package + + First, enter the command below to create a new package in your TurboStarter application: + + ```bash + turbo gen package + ``` + + Turborepo will ask you to enter the name of the package you want to create. Enter the name of the package you want to create and press enter. + + If you don't want to add dependencies to your package, you can skip this step by pressing enter. + + The command will have generated a new package under packages named `@turbostarter/`. If you named it `example`, the package will be named `@turbostarter/example`. + + + + ## Export a module from your package + + By default, the package exports a single module using the `index.ts` file. You can add more exports by creating new files in the package directory and exporting them from the `index.ts` file or creating export files in the package directory and adding them to the `exports` field in the `package.json` file. + + ### From `index.ts` file + + The easiest way to export a module from a package is to create a new file in the package directory and export it from the `index.ts` file. + + ```ts title="packages/example/src/module.ts" + export function example() { + return "example"; + } + ``` + + Then, export the module from the `index.ts` file. + + ```ts title="packages/example/src/index.ts" + export * from "./module"; + ``` + + ### From `exports` field in `package.json` + + **This can be very useful for tree-shaking.** Assuming you have a file named `module.ts` in the package directory, you can export it by adding it to the `exports` field in the `package.json` file. + + ```json title="packages/example/package.json" + { + "exports": { + ".": "./src/index.ts", + "./module": "./src/module.ts" + } + } + ``` + + **When to do this?** + + 1. when exporting two modules that don't share dependencies to ensure better tree-shaking. For example, if your exports contains both client and server modules. + 2. for better organization of your package + + For example, create two exports `client` and `server` in the package directory and add them to the `exports` field in the `package.json` file. + + ```json title="packages/example/package.json" + { + "exports": { + ".": "./src/index.ts", + "./client": "./src/client.ts", + "./server": "./src/server.ts" + } + } + ``` + + 1. The `client` module can be imported using `import { client } from '@turbostarter/example/client'` + 2. The `server` module can be imported using `import { server } from '@turbostarter/example/server'` + + + + ## Use the package in your extension + + You can now use the package in your extension by importing it using the package name: + + ```ts title="app/popup/index.tsx" + import { example } from "@turbostarter/example"; + + console.log(example()); + ``` + + + +Et voilà! You have successfully added a new package to your TurboStarter extension. 🎉 diff --git a/.context/turbostarter-framework-context/sections/extension/customization/components.md b/.context/turbostarter-framework-context/sections/extension/customization/components.md new file mode 100644 index 0000000..bfaf226 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/customization/components.md @@ -0,0 +1,120 @@ +--- +title: Components +description: Manage and customize your extension components. +url: /docs/extension/customization/components +--- + +# Components + +For the components part, we're using [shadcn/ui](https://ui.shadcn.com) for atomic, accessible and highly customizable components. + + + shadcn/ui is a powerful tool that allows you to generate pre-designed + components with a single command. It's built with Tailwind CSS and Radix UI, + and it's highly customizable. + + +TurboStarter defines two packages that are responsible for the UI part of your app: + +* `@turbostarter/ui` - shared styles, [themes](/docs/web/customization/styling#themes) and assets (e.g. icons) +* `@turbostarter/ui-web` - pre-built UI web components, ready to use in your app + +## Adding a new component + +There are basically two ways to add a new component: + + + + TurboStarter is fully compatible with [shadcn CLI](https://ui.shadcn.com/docs/cli), so you can generate new components with single command. + + Run the following command from the **root** of your project: + + ```bash + pnpm --filter @turbostarter/ui-web ui:add + ``` + + This will launch an interactive command-line interface to guide you through the process of adding a new component where you can pick which component you want to add. + + ```bash + Which components would you like to add? > Space to select. A to toggle all. + Enter to submit. + + ◯ accordion + ◯ alert + ◯ alert-dialog + ◯ aspect-ratio + ◯ avatar + ◯ badge + ◯ button + ◯ calendar + ◯ card + ◯ checkbox + ``` + + Newly created components will appear in the `packages/ui/web/src` directory. + + + + You can always copy-paste a component from the [shadcn/ui](https://ui.shadcn.com/docs/components) website and modify it to your needs. + + This is possible, because the components are headless and don't need (in most cases) any additional dependencies. + + Copy code from the website, create a new file in the `packages/ui/web/src` directory and paste the code into the file. + + + + + Keep in mind that you should always try to keep shared components as atomic as possible. This will make it easier to reuse them and to build specific views by composition. + + E.g. include components like `Button`, `Input`, `Card`, `Dialog` in shared package, but keep specific components like `LoginForm` in your app directory. + + +## Using components + +Each component is a standalone entity which has a separate export from the package. It helps to keep things modular, avoid unnecessary dependencies and make tree-shaking possible. + +To import a component from the UI package, use the following syntax: + +```tsx title="components/my-component.tsx" +// [!code word:card] +import { + Card, + CardContent, + CardHeader, + CardFooter, + CardTitle, + CardDescription, +} from "@turbostarter/ui-web/card"; +``` + +Then you can use it to build a component specific to your app: + +```tsx title="components/my-component.tsx" +export function MyComponent() { + return ( + + + My Component + + +

My Component Content

+
+ + + +
+ ); +} +``` + + + We recommend using [v0](https://v0.dev) to generate layouts for your app. It's a powerful tool that allows you to generate layouts from the natural language instructions. + + Of course, **it won't replace a designer**, but it can be a good starting point for your layout. + + + + + + + diff --git a/.context/turbostarter-framework-context/sections/extension/customization/styling.md b/.context/turbostarter-framework-context/sections/extension/customization/styling.md new file mode 100644 index 0000000..aef741f --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/customization/styling.md @@ -0,0 +1,159 @@ +--- +title: Styling +description: Get started with styling your extension. +url: /docs/extension/customization/styling +--- + +# Styling + +To build the extension interface TurboStarter comes with [Tailwind CSS](https://tailwindcss.com/) and [Radix UI](https://www.radix-ui.com/) pre-configured. + + + The combination of Tailwind CSS and Radix UI gives ready-to-use, accessible UI components that can be fully customized to match your brands design. + + +## Tailwind configuration + +In the `packages/ui/shared/src/styles` directory, you will find shared CSS files with Tailwind CSS configuration. To change global styles, you can edit the files in this folder. + +Here is an example of a shared CSS file that includes the Tailwind CSS configuration: + +```css title="packages/ui/shared/src/styles/globals.css" +@import "tailwindcss"; +@import "./themes.css"; + +@custom-variant dark (&:is(.dark *)); + +:root { + --radius: 0.65rem; +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + + ... +} +``` + +For colors, we rely strictly on [CSS Variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties) in [OKLCH](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch) format to allow for easy theme management without the need for any JavaScript. + +Also, each app has its own `globals.css` file, which extends the shared config and allows you to override the global styles. + +Here is an example of an extension's `globals.css` file: + +```css title="apps/extension/src/assets/styles/globals.css" +@import url("https://fonts.googleapis.com/css2?family=Geist+Mono:wght@100..900&family=Geist:wght@100..900&display=swap"); +@import "@turbostarter/ui/globals.css"; +@import "@turbostarter/ui-web/globals.css"; + +@theme { + --font-sans: "Geist", sans-serif; + --font-mono: "Geist Mono", monospace; +} +``` + +This way, we maintain a separation of concerns and a clear structure for the Tailwind CSS configuration. + +## Themes + +TurboStarter comes with **9+** predefined themes, which you can use to quickly change the look and feel of your app. + +They're defined in the `packages/ui/shared/src/styles/themes` directory. Each theme is a set of variables that can be overridden: + +```ts title="packages/ui/shared/src/styles/themes/colors/orange.ts" +export const orange = { + light: { + background: [1, 0, 0], + foreground: [0.141, 0.005, 285.823], + card: [1, 0, 0], + "card-foreground": [0.141, 0.005, 285.823], + ... + } +} satisfies ThemeColors; +``` + +Each variable is stored as a [OKLCH](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch) array, which is then converted to a CSS variable at build time (by our custom build script). That way we can ensure full type-safety and reuse themes across different parts of our apps (e.g. use the same theme in emails). + +Feel free to add your own themes or override the existing ones to match your brand's identity. + +To apply a theme to your app, you can use the `data-theme` attribute on your layout wrapper for each part of the extension: + +```tsx title="modules/common/layout/layout.tsx" +import { StorageKey, useStorage } from "~/lib/storage"; + +export const Layout = ({ children }: { children: React.ReactNode }) => { + const { data } = useStorage(StorageKey.THEME); + + return ( +
+ {children} +
+ ); +}; +``` + +In TurboStarter, we're using [Storage API](/docs/extension/structure/storage) to persist the user's theme selection and then apply it to the `div#main` element. + +## Dark mode + +The starter kit comes with a built-in dark mode support. + +Each theme has a corresponding dark mode variables which are used to change the theme to its dark mode counterpart. + +```ts title="packages/ui/shared/src/styles/themes/colors/orange.ts" +export const orange = { + light: {}, + dark: { + background: [0.141, 0.005, 285.823], + foreground: [0.985, 0, 0], + card: [0.21, 0.006, 285.885], + "card-foreground": [0.985, 0, 0], + ... + } +} satisfies ThemeColors; +``` + +Because the dark variant is defined to use a class (`@custom-variant dark (&:is(.dark *))`) in the shared Tailwind configuration, we need to add the `dark` class to the root element to apply dark mode styles. + +The same as for the theme color, we're using here the [Storage API](/docs/extension/structure/storage) to persist the user's dark mode selection and then apply correct class name to the root `div` element: + +```tsx title="modules/common/layout/layout.tsx" +import { StorageKey, useStorage } from "~/lib/storage"; + +export const Layout = ({ children }: { children: React.ReactNode }) => { + const { data } = useStorage(StorageKey.THEME); + + return ( +
+ {children} +
+ ); +}; +``` + +You can also define the default theme mode and color in the [app configuration](/docs/extension/configuration/app). + + + + + + diff --git a/.context/turbostarter-framework-context/sections/extension/database.md b/.context/turbostarter-framework-context/sections/extension/database.md new file mode 100644 index 0000000..4dc0512 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/database.md @@ -0,0 +1,39 @@ +--- +title: Database +description: Get started with the database. +url: /docs/extension/database +--- + +# Database + + + To enable communication between your WXT extension and the server in a production environment, the web application with Hono API must be deployed first. + + + + + + + + +As browser extensions use only client-side code, **there's no way to interact with the database directly**. + +Also, you should avoid any workarounds to interact with the database directly, because it can lead to leaking your database credentials and other security issues. + +## Recommended approach + +You can safely use the [API](/docs/extension/api/overview) and invoke procedures which will run queries on the database. + +To do this you need to set up the database on the [web, server side](/docs/web/database/overview) and then use the [API client](/docs/extension/api/client) to interact with it. + +Learn more about its configuration in the web part of the docs, especially in the following sections: + + + + + + + + + + diff --git a/.context/turbostarter-framework-context/sections/extension/extras.md b/.context/turbostarter-framework-context/sections/extension/extras.md new file mode 100644 index 0000000..2d4b0fb --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/extras.md @@ -0,0 +1,66 @@ +--- +title: Extras +description: See what you get together with the code. +url: /docs/extension/extras +--- + +# Extras + +## Tips and Tricks + +In many places, next to the code you will find some marketing tips, design suggestions, and potential risks. This is to help you build a better product and avoid common pitfalls. + +```tsx title="Hero.tsx" +return ( +
+ {/* 💡 Use something that user can visualize e.g. + "Ship your startup while on the toilet" */} +

Best startup on the world

+
+); +``` + +### Submission tips + +When it comes to mobile app and browser extension, you must submit your product to review from Apple/Google etc. We have some tips for you to make sure your submission goes smoothly. + +```json title="app.json" +{ + "ios": { + "infoPlist": { + /* 🍎 add descriptive justification of using this permission on iOS */ + "NSCameraUsageDescription": "This app uses the camera to scan barcodes on event tickets." + } + } +} +``` + +As well as providing you with the info on how to make your store listings better: + +```json title="package.json" +{ + "manifest": { + /* 💡 Use localized messages to get more visibility in web stores */ + "name": "__MSG_extensionName__", + "default_locale": "en" + } +} +``` + +## Discord community + +We have a Discord community where you can ask questions and share your projects. It's a great place to get help and meet other developers. Check more details at [/discord](/discord). + + + +![Discord](/images/docs/discord.png) + +## 25+ SaaS Ideas + +Not sure what to build? We have a list of **25+** SaaS ideas that you can use to get started 🔥 + +Grouped by category, these ideas are a great way to get inspired and start building your next project. + +Including design, copies, marketing tips and potential risks, this list is a great resource for anyone looking to build a SaaS product. + +![SaaS Ideas](/images/docs/saas-ideas.png) diff --git a/.context/turbostarter-framework-context/sections/extension/faq.md b/.context/turbostarter-framework-context/sections/extension/faq.md new file mode 100644 index 0000000..7301026 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/faq.md @@ -0,0 +1,92 @@ +--- +title: FAQ +description: Find answers to common technical questions. +url: /docs/extension/faq +--- + +# FAQ + +## Why isn't everything hidden and configured with one BIG config file? + +TurboStarter intentionally exposes the underlying code rather than hiding it behind configuration files (like some starters do). This design choice follows our **you own your code** philosophy, giving you full control and flexibility over your codebase. + +While a single config file might seem simpler initially, it often becomes restrictive when you need to customize functionality beyond what the config allows. With direct access to the code, you can modify any part of the system to match your specific requirements. + +## I don't know some technology! Should I buy TurboStarter? + +You should be prepared for a learning curve or consider learning it first. However, TurboStarter will still work for you if you're willing to learn. + +Even without knowing some technologies, you can still use the rest of the features. + +## I don't need mobile app or browser extension, what should I do? + +You can simply ignore the mobile app and browser extension parts of the project. You can remove the `apps/mobile` and `apps/extension` directories from the project. + +The modular nature of TurboStarter allows you to remove parts of the project that you don't need without affecting the rest of the stack. + +## I want to use a different provider for X + +Sure! TurboStarter is designed to be modular, so configuring new provider (e.g. for emails, billing or any other service) is straightforward. You just need to make sure your configuration is compatible with common interface to be able to plug it into the codebase. + +## Will you add more packages in the future? + +Yes, we will keep updating TurboStarter with new packages and features. This kit is designed to be modular, allowing for new features and packages to be added without interfering with your existing code. You can always [update your project](/docs/web/installation/update) to the latest version. + +## Can I use this kit for a non-SaaS project? + +This kit is mainly designed for SaaS projects. If you're building something other than a SaaS, the Next.js SaaS Boilerplate might include features you don't need. You can still use it for non-SaaS projects, but you may need to remove or modify features that are specific to SaaS use cases. + +## Can I use personal accounts only? + +Yes! You can disable team accounts and have personal accounts only by setting a feature flag. + +## Does it set up the production instance for me? + +No, TurboStarter does not set up the production instance for you. This includes setting up databases, Stripe, or any other services you need. TurboStarter does not have access to your Stripe or Resend accounts, so setup on your end is required. TurboStarter provides the codebase and documentation to help you set up your SaaS project. + +## Does the starter include Solito? + +No. Solito will not be included in this repo. It is a great tool if you want to share code between your Next.js and Expo app. However, the main purpose of this repo is not the integration between Next.js and Expo — it's the code splitting of your SaaS platforms into a monorepo. You can utilize the monorepo with multiple apps, and it can be any app such as Vite, Electron, etc. + +Integrating Solito into this repo isn't hard, and there are a few [official templates](https://github.com/nandorojo/solito/tree/master/example-monorepos) by the creators of Solito that you can use as a reference. + +## Does this pattern leak backend code to my client applications? + +No, it does not. The `api` package should only be a production dependency in the Next.js application where it's served. The Expo app, browser extension, and all other apps you may add in the future should only add the `api` package as a dev dependency. This lets you have full type safety in your client applications while keeping your backend code safe. + +If you need to share runtime code between the client and server, you can create a separate `shared` package for this and import it on both sides. + +## How do I get support if I encounter issues? + +For support, you can: + +1. Visit our [Discord](https://discord.gg/KjpK2uk3JP) +2. Contact us via support email ([hello@turbostarter.dev](mailto:hello@turbostarter.dev)) + +## Are there any example projects or demos? + +Yes - feel free to check out our demo app at [demo.turbostarter.dev](https://demo.turbostarter.dev). Also, you can get inspired by projects built by our customers - take a look at [Showcase](/#showcase). + +## How do I deploy my application? + +Please check the [production checklist](/docs/web/deployment/checklist) for more information. + +## How do I update my project when a new version of the boilerplate is released? + +Please read the [documentation for updating your TurboStarter code](/docs/web/installation/update). + +## Can I use the React package X with this kit? + +Yes, you can use any React package with this kit. The kit is based on React, so you are generally only constrained by the underlying technologies and not by the kit itself. Since you own and can edit all the code, you can adapt the kit to your needs. However, if there are limitations with the underlying technology, you might need to work around them. + +## Can I integrate TurboStarter into an existing project? + +TurboStarter is a full-stack starter intended to be used as the foundation of your app. You can copy individual modules or patterns into an existing codebase, but retrofitting the entire starter into a mature project is typically not recommended and is not officially supported. If you choose to copy parts, prefer isolating boundaries (e.g., `packages/` modules) and aligning interfaces first. + +## Where can I deploy my application? + +TurboStarter targets modern Node.js/Next.js runtimes. You can deploy to providers that support these environments, such as [Vercel](/docs/web/deployment/vercel), [Railway](/docs/web/deployment/railway), [Render](/docs/web/deployment/render), [Fly](/docs/web/deployment/fly), or [Netlify](/docs/web/deployment/netlify) - following their Next.js guidance. Review our [production checklist](/docs/web/deployment/checklist) before going live. + +## Can I easily swap providers (billing, email, etc.)? + +Yes. The starter organizes integrations behind clear interfaces so you can replace providers (e.g., billing or email) with minimal surface changes. Keep your implementation behind a module boundary and adapt to the existing types to avoid ripple effects. diff --git a/.context/turbostarter-framework-context/sections/extension/index.md b/.context/turbostarter-framework-context/sections/extension/index.md new file mode 100644 index 0000000..c927379 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/index.md @@ -0,0 +1,336 @@ +--- +title: Introduction +description: Get started with TurboStarter extension kit. +url: /docs/extension +--- + +# Introduction + +Welcome to the TurboStarter documentation. This is your starting point for learning about the starter kit, its structure, features, and how to use it for your app development. + + + +## What is TurboStarter? + +TurboStarter is a fullstack starter kit that helps you build scalable and production-ready web apps, mobile apps, and browser extensions in minutes. + +Looking to bootstrap your project quickly? Check out our [TurboStarter CLI guide](/blog/the-only-turbo-cli-you-need-to-start-your-next-project-in-seconds) to get started in seconds. + +## Demo apps + +TurboStarter provides a suite of live demo applications you can try instantly - right in your browser, on your phone, or via browser extensions. Try them live by clicking the buttons below. + + + +## Principles + +TurboStarter is built with the following principles: + +* **As simple as possible** - It should be easy to understand, easy to use, and strongly avoid overengineering things. +* **As few dependencies as possible** - It should have as few dependencies as possible to allow you to take full control over every part of the project. +* **As performant as possible** - It should be fast and light without any unnecessary overhead. + +## Features + +Before diving into the technical details, let's overview the features TurboStarter provides. + +### Multi-platform development + +* [Web](/docs/web/stack): Build web apps with React, Next.js, and Tailwind CSS. +* [Mobile](/docs/mobile/stack): Build mobile apps with React Native and Expo. +* [Browser extension](/docs/extension/stack): Build browser extensions with React and WXT. + +For those interested in AI development, check out our dedicated [TurboStarter AI documentation](/ai/docs) with specialized features for building AI-powered applications. + + + Most features are available on all platforms. You can use the **same codebase** to build web, mobile, and browser extension apps. + + +### Authentication + + + + + + + + + + + + + + + + + + + +### Organizations/teams + + + + + + + + + + + +### Billing + + + + + + + + + + + + + + + +### Database + + + + + + + + + + + +### API + + + + + + + + + + + + + +### Admin + + + + + + + + + + + +### AI + + + + Seamless integration of OpenAI, Anthropic, Groq, Mistral, and Gemini. For more advanced AI features, check out [TurboStarter AI](/ai/docs). + + + + + + + + + +### Internationalization + + + + + + + + + + + +### Emails + + + + + + + + + + + +### Landing page + + + + + + + + + + + + + + + +### Marketing + + + + + + + + + + + + + + + + + +### Storage + + + + + + + +### CMS + + + + + + + +### Theming + + + + + + + + + + + +### Analytics + + + + + + + + + + + +### Monitoring + + + + + + + + + + + +### Deployment + + + + + + + + + + + +### Testing + + + + + + + + + + + +## Use like LEGO blocks + +The biggest advantage of TurboStarter is its modularity. You can use the entire stack or just the parts you need. It's like LEGO blocks - you can build anything you want with it. + +If you don't need a specific feature, feel free to remove it without affecting the rest of the stack. + +This approach allows for: + +* **Easy feature integration** - plug new features into the kit with minimal changes. +* **Simplified maintenance** - keep the codebase clean and maintainable. +* **Core feature separation** - distinguish between core features and custom features. +* **Additional modules** - easily add modules like billing, CMS, monitoring, logger, mailer, and more. + +## Scope of this documentation + +While building a SaaS application involves many moving parts, this documentation focuses specifically on TurboStarter. For in-depth information on the underlying technologies, please refer to their respective official documentation. + +This documentation will guide you through configuring, running, and deploying the kit, and will provide helpful links to the official documentation of technologies where necessary. + +## `llms.txt` + +You can access the entire TurboStarter documentation in Markdown format at [/llms.txt](/llms.txt). This can be used to ask any LLM (assuming it has a big enough context window) questions about the TurboStarter based on the most up-to-date documentation. + +### Example usage + +For instance, to prompt an LLM with questions about the TurboStarter: + +1. Copy the documentation contents from [/llms.txt](/llms.txt) +2. Use the following prompt format: + +``` +Documentation: +{paste documentation here} +--- +Based on the above documentation, answer the following: +{your question} +``` + +## Enjoy! + +This documentation is designed to be easy to follow and understand. If you have any questions or need help, feel free to reach out to us at [hello@turbostarter.dev](mailto:hello@turbostarter.dev). + +Explore new features, build amazing apps, and have fun! 🚀 diff --git a/.context/turbostarter-framework-context/sections/extension/installation/clone.md b/.context/turbostarter-framework-context/sections/extension/installation/clone.md new file mode 100644 index 0000000..8ccaa90 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/installation/clone.md @@ -0,0 +1,65 @@ +--- +title: Cloning repository +description: Get the code to your local machine and start developing your extension. +url: /docs/extension/installation/clone +--- + +# Cloning repository + + + Ensure you have Git installed on your local machine before proceeding. You can download Git from [here](https://git-scm.com). + + +## Git clone + +Clone the repository using the following command: + +```bash +git clone git@github.com:turbostarter/core +``` + +By default, we're using [SSH](https://docs.github.com/en/authentication/connecting-to-github-with-ssh) for all Git commands. If you don't have it configured, please refer to the [official documentation](https://docs.github.com/en/authentication/connecting-to-github-with-ssh) to set it up. + +Alternatively, you can use HTTPS to clone the repository: + +```bash +git clone https://github.com/turbostarter/core +``` + +Another alternative could be to use the [Github CLI](https://cli.github.com/) or [Github Desktop](https://desktop.github.com/) for Git operations. + + + +## Git remote + +After cloning the repository, remove the original origin remote: + +```bash +git remote rm origin +``` + +Add the upstream remote pointing to the original repository to pull updates: + +```bash +git remote add upstream git@github.com:turbostarter/core +``` + +Once you have your own repository set up, add your repository as the origin: + +```bash +git remote add origin +``` + + + +## Staying up to date + +To pull updates from the upstream repository, run the following command daily (preferably with your morning coffee ☕): + +```bash +git pull upstream main +``` + +This ensures your repository stays up to date with the latest changes. + +Check [Updating codebase](/docs/web/installation/update) for more details on updating your codebase. diff --git a/.context/turbostarter-framework-context/sections/extension/installation/commands.md b/.context/turbostarter-framework-context/sections/extension/installation/commands.md new file mode 100644 index 0000000..53df222 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/installation/commands.md @@ -0,0 +1,353 @@ +--- +title: Common commands +description: Learn about common commands you need to know to work with the extension project. +url: /docs/extension/installation/commands +--- + +# Common commands + + + For sure, you don't need these commands to kickstart your project, but it's useful to know they exist for when you need them. + + + + You can set up aliases for these commands in your shell configuration file. For example, you can set up an alias for `pnpm` to `p`: + + ```bash title="~/.bashrc" + alias p='pnpm' + ``` + + Or, if you're using [Zsh](https://ohmyz.sh/), you can add the alias to `~/.zshrc`: + + ```bash title="~/.zshrc" + alias p='pnpm' + ``` + + Then run `source ~/.bashrc` or `source ~/.zshrc` to apply the changes. + + You can now use `p` instead of `pnpm` in your terminal. For example, `p i` instead of `pnpm install`. + + + + To inject environment variables into the command you run, prefix it with `with-env`: + + ```bash + pnpm with-env + ``` + + For example, `pnpm with-env pnpm build` will run `pnpm build` with the environment variables injected. + + Some commands, like `pnpm dev`, automatically inject the environment variables for you. + + +## Installing dependencies + +To install the dependencies, run: + +```bash +pnpm install +``` + +## Starting development server + +Start development server by running: + +```bash +pnpm dev +``` + +## Building project + +To build the project (including all apps and packages), run: + +```bash +pnpm build +``` + +## Building specific app/package + +To build a specific app/package, run: + +```bash +pnpm turbo build --filter= +``` + +## Cleaning project + +To clean the project, run: + +```bash +pnpm clean +``` + +Then, reinstall the dependencies: + +```bash +pnpm install +``` + +## Formatting code + +To check for formatting errors using Prettier, run: + +```bash +pnpm format +``` + +To fix formatting errors using Prettier, run: + +```bash +pnpm format:fix +``` + +## Linting code + +To check for linting errors using ESLint, run: + +```bash +pnpm lint +``` + +To fix linting errors using ESLint, run: + +```bash +pnpm lint:fix +``` + +## Typechecking + +To typecheck the code using TypeScript for any type errors, run: + +```bash +pnpm typecheck +``` + +## Adding UI components + + + + To add a new web component, run: + + ```bash + pnpm --filter @turbostarter/ui-web ui:add + ``` + + This command will add and export a new component to `@turbostarter/ui-web` package. + + + + To add a new mobile component, run: + + ```bash + pnpm --filter @turbostarter/ui-mobile ui:add + ``` + + This command will add and export a new component to `@turbostarter/ui-mobile` package. + + + +## Services commands + + + To run the services containers locally, you need to have [Docker](https://www.docker.com/) installed on your machine. + + You can always use the cloud-hosted solution (e.g. [Neon](https://neon.tech/), [Turso](https://turso.tech/) for database) for your projects. + + +We have a few commands to help you manage the services containers (for local development). + +### Starting containers + +To start the services containers, run: + +```bash +pnpm services:start +``` + +It will run all the services containers. You can check their configs in `docker-compose.yml`. + +### Setting up services + +To setup all the services, run: + +```bash +pnpm services:setup +``` + +It will start all the services containers and run necessary setup steps. + +### Stopping containers + +To stop the services containers, run: + +```bash +pnpm services:stop +``` + +### Displaying status + +To check the status and logs of the services containers, run: + +```bash +pnpm services:status +``` + +### Displaying logs + +To display the logs of the services containers, run: + +```bash +pnpm services:logs +``` + +### Database commands + +We have a few commands to help you manage the database leveraging [Drizzle CLI](https://orm.drizzle.team/kit-docs/commands). + +#### Generating migrations + +To generate a new migration, run: + +```bash +pnpm with-env turbo db:generate +``` + +It will create a new migration `.sql` file in the `packages/db/migrations` folder. + +#### Running migrations + +To run the migrations against the db, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:migrate +``` + +It will apply all the pending migrations. + +#### Pushing changes directly + + + Make sure you know what you're doing before pushing changes directly to the db. + + +To push changes directly to the db, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:push +``` + +It lets you push your schema changes directly to the database and omit managing SQL migration files. + +#### Checking database status + +To check the status of the database, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:status +``` + +It will display the status of the applied migrations and the pending ones. + +```bash +Applied migrations: +- 0000_cooing_vargas +- 0001_curious_wallflower +- 0002_good_vertigo +- 0003_peaceful_devos +- 0004_fat_mad_thinker +- 0005_yummy_bucky +- 0006_glorious_vargas + +Pending migrations: +- 0007_nebulous_havok +``` + +#### Resetting database + +To reset the database, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:reset +``` + +It will reset the database to the initial state. + +#### Seeding database + +To seed the database with some example data (for development purposes), run: + +```bash +pnpm with-env turbo db:seed +``` + +It will populate your database with some example data. + +#### Checking database + +To check the database schema consistency, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:check +``` + +#### Studying database + +To study the database schema in the browser, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:studio +``` + +This will start the Studio on [https://local.drizzle.studio](https://local.drizzle.studio). + +## Tests commands + +### Running tests + +To run the tests, run: + +```bash +pnpm test +``` + +This will run all the tests in the project using Turbo tasks. As it leverages Turbo caching, it's [recommended](/docs/web/tests/unit#configuration) to run it in your CI/CD pipeline. + +### Running tests projects + +To run tests for all Vitest [Test Projects](https://vitest.dev/guide/projects), run: + +```bash +pnpm test:projects +``` + +This will run all the tests in the project using Vitest. + +### Watching tests + +To watch the tests, run: + +```bash +pnpm test:projects:watch +``` + +This will watch the tests for all [Test Projects](https://vitest.dev/guide/projects) and run them automatically when you make changes. + +### Generating code coverage + +To generate code coverage report, run: + +```bash +pnpm turbo test:coverage +``` + +This will generate a code coverage report in the `coverage` directory under `tooling/vitest` package. + +### Viewing code coverage + +To preview the code coverage report in the browser, run: + +```bash +pnpm turbo test:coverage:view +``` + +This will launch the report's `.html` file in your default browser. diff --git a/.context/turbostarter-framework-context/sections/extension/installation/conventions.md b/.context/turbostarter-framework-context/sections/extension/installation/conventions.md new file mode 100644 index 0000000..2588427 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/installation/conventions.md @@ -0,0 +1,86 @@ +--- +title: Conventions +description: Some standard conventions used across TurboStarter codebase. +url: /docs/extension/installation/conventions +--- + +# Conventions + +You're not required to follow these conventions: they're simply a standard set of practices used in the core kit. If you like them - we encourage you to keep these during your usage of the kit - so to have consistent code style that you and your teammates understand. + +## Turborepo Packages + +In this project, we use [Turborepo packages](https://turbo.build/repo/docs/core-concepts/internal-packages) to define reusable code that can be shared across multiple applications. + +* **Apps** are used to define the main application, including routing, layout, and global styles. +* **Packages** shares reusable code add functionalities across multiple applications. They're configurable from the main application. + + + **Recommendation:** Do not create a package for your app code unless you plan to reuse it across multiple applications or are experienced in writing library code. + + If your application is not intended for reuse, keep all code in the app folder. This approach saves time and reduces complexity, both of which are beneficial for fast shipping. + + **Experienced developers:** If you have the experience, feel free to create packages as needed. + + +## Imports and Paths + +When importing modules from packages or apps, use the following conventions: + +* **From a package:** Use `@turbostarter/package-name` (e.g., `@turbostarter/ui`, `@turbostarter/api`, etc.). +* **From an app:** Use `~/` (e.g., `~/components`, `~/config`, etc.). + +## Enforcing conventions + +* [Prettier](https://prettier.io/) is used to enforce code formatting. +* [ESLint](https://eslint.org/) is used to enforce code quality and best practices. +* [TypeScript](https://www.typescriptlang.org/) is used to enforce type safety. + + + + + + + + + +## Code health + +TurboStarter provides a set of tools to ensure code health and quality in your project. + +### Github Actions + +By default, TurboStarter sets up Github Actions to run tests on every push to the repository. You can find the Github Actions configuration in the `.github/workflows` directory. + +The workflow has multiple stages: + +* `format` - runs Prettier to format the code. +* `lint` - runs ESLint to check for linting errors. +* `typecheck` - runs TypeScript to check for type errors. + +### Git hooks + +Together with TurboStarter we have set up a `commit-msg` hook which will check if your commit message follows the [conventional commit](https://www.conventionalcommits.org/en/v1.0.0/) message format. This is important for generating changelogs and keeping a clean commit history. + +Although we didn't ship any pre-commit hooks (we believe in shipping fast with moving checking code responsibility to CI), you can easily add them by using [Husky](https://typicode.github.io/husky/#/). + +#### Setting up the Pre-Commit Hook + +To do so, create a `pre-commit` file in the `./..husky` directory with the following content: + +```bash +#!/bin/sh + +pnpm typecheck +pnpm lint +``` + +Turborepo will execute the commands for all the affected packages - while skipping the ones that are not affected. + +#### Make the Pre-Commit Hook Executable + +```bash +chmod +x ./.husky/pre-commit +``` + +To test the pre-commit hook, try to commit a file with linting errors or type errors. The commit should fail, and you should see the error messages in the console. diff --git a/.context/turbostarter-framework-context/sections/extension/installation/dependencies.md b/.context/turbostarter-framework-context/sections/extension/installation/dependencies.md new file mode 100644 index 0000000..14109c9 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/installation/dependencies.md @@ -0,0 +1,73 @@ +--- +title: Managing dependencies +description: Learn how to manage dependencies in your project. +url: /docs/extension/installation/dependencies +--- + +# Managing dependencies + +As the package manager we chose [pnpm](https://pnpm.io/). + + + It is a fast, disk space efficient package manager that uses hard links and symlinks to save one version of a module only ever once on a disk. It also has a great [monorepo support](https://pnpm.io/workspaces). Of course, you can change it to use [Bun](https://bunpkg.com), [yarn](https://yarnpkg.com) or [npm](https://www.npmjs.com) with minimal effort. + + +## Install dependency + +To install a package you need to decide whether you want to install it to the root of the monorepo or to a specific workspace. Installing it to the root makes it available to all packages, while installing it to a specific workspace makes it available only to that workspace. + +To install a package globally, run: + +```bash +pnpm add -w +``` + +To install a package to a specific workspace, run: + +```bash +pnpm add --filter +``` + +For example: + +```bash +pnpm add --filter @turbostarter/ui motion +``` + +It will install `motion` to the `@turbostarter/ui` workspace. + +## Remove dependency + +Removing a package is the same as installing but with the `remove` command. + +To remove a package globally, run: + +```bash +pnpm remove -w +``` + +To remove a package from a specific workspace, run: + +```bash +pnpm remove --filter +``` + +## Update a package + +Updating is a bit easier since there is a nice way to update a package in all workspaces at once: + +```bash +pnpm update -r +``` + + + When you update a package, pnpm will respect the [semantic versioning](https://docs.npmjs.com/about-semantic-versioning) rules defined in the `package.json` file. If you want to update a package to the latest version, you can use the `--latest` flag. + + +## Renovate bot + +By default, TurboStarter comes with [Renovate](https://www.npmjs.com/package/renovate) enabled. It is a tool that helps you manage your dependencies by automatically creating pull requests to update your dependencies to the latest versions. You can find its configuration in the `.github/renovate.json` file. Learn more about it in the [official docs](https://docs.renovatebot.com/configuration-options/). + +When it creates a pull request, it is treated as a normal PR, so all tests and preview deployments will run. **It is recommended to always preview and test the changes in the staging environment before merging the PR to the main branch to avoid breaking the application.** + + diff --git a/.context/turbostarter-framework-context/sections/extension/installation/development.md b/.context/turbostarter-framework-context/sections/extension/installation/development.md new file mode 100644 index 0000000..4cf907c --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/installation/development.md @@ -0,0 +1,139 @@ +--- +title: Development +description: Get started with the code and develop your browser extension. +url: /docs/extension/installation/development +--- + +# Development + +## Prerequisites + +To get started with TurboStarter, ensure you have the following installed and set up: + +* [Node.js](https://nodejs.org/en) (22.x or higher) +* [Docker](https://www.docker.com) (only if you want to use local services e.g. database) +* [pnpm](https://pnpm.io) + +## Project development + + + + ### Install dependencies + + Install the project dependencies by running the following command: + + ```bash + pnpm i + ``` + + + It is a fast, disk space efficient package manager that uses hard links and symlinks to save one version of a module only ever once on a disk. It also has a great [monorepo support](https://pnpm.io/workspaces). Of course, you can change it to use [Bun](https://bunpkg.com), [yarn](https://yarnpkg.com) or [npm](https://www.npmjs.com) with minimal effort. + + + + + ### Setup environment variables + + Create a `.env.local` files from `.env.example` files and fill in the required environment variables. + + You can use the following command to recursively copy the `.env.example` files to the `.env.local` files: + + + + ```bash + find . -name ".env.example" -exec sh -c 'cp "$1" "${1%.example}.local"' _ {} \; + ``` + + + + ```bash + Get-ChildItem -Recurse -Filter ".env.example" | ForEach-Object { + Copy-Item $_.FullName ($\_.FullName -replace '\.example$', '.local') + } + ``` + + + + Check [Environment variables](/docs/extension/configuration/environment-variables) for more details on setting up environment variables. + + + + ### Setup services + + If you want to use local services like database etc. (**recommended for development purposes**), ensure Docker is running, then setup them with: + + ```bash + pnpm services:setup + ``` + + This command initiates the containers and runs necessary setup steps, ensuring your services are up to date and ready to use. + + + + ### Start development server + + To start the application development server, run: + + ```bash + pnpm dev + ``` + + Your development server should now be running 🎉 + + WXT will create a dev bundle for your extension and start a live-reloading development server, which will automatically update your extension bundle and reload your browser on source code changes. + + It also makes the icon grayscale to distinguish between development and production extension bundles. + + + + ### Load the extension + + + + Head over to `chrome://extensions` and enable **Developer Mode**. + + ![Developer mode](/images/docs/extension/chrome/developer-mode.png) + + Click on "Load Unpacked" and navigate to your extension's `apps/extension/build/chrome-mv3` directory. + + To see your popup, click on the puzzle piece icon on the Chrome toolbar, and click on your extension. + + ![Pin to toolbar](/images/docs/extension/chrome/pin.png) + + + Pin your extension to the Chrome toolbar for easy access by clicking the pin button. + + + + + Head over to `about:debugging` and click on "This Firefox". + + Click on "Load Temporary Add-on" and navigate to your extension's `apps/extension/build/firefox-mv2` directory. Pick any file to load the extension. + + ![Load temporary add-on](/images/docs/extension/firefox/load.png) + + The extension now installs, and remains installed until you restart Firefox. + + To see your popup, click on your extension icon on the Firefox toolbar. + + ![Popup](/images/docs/extension/firefox/popup.png) + + + Loaded extension starts as pinned on the Firefox toolbar. Don't remove it to easily access it later. + + + + + + You can also configure your development server to automatically start the browser when you start the server. To do it, create a `web-ext.config.ts` file in a root of your extension and configure it with your browser [binaries](https://wxt.dev/guide/essentials/config/browser-startup.html#set-browser-binaries) and [argumens](https://wxt.dev/guide/essentials/config/browser-startup.html#persist-data). + + Learn more in the [official documentation](https://wxt.dev/guide/essentials/config/browser-startup.html). + + + + + ### Publish to stores + + When you're ready to publish the project to the stores, follow the [guidelines](/docs/extension/marketing) and [checklist](/docs/extension/publishing/checklist) to ensure everything is set up correctly. + + diff --git a/.context/turbostarter-framework-context/sections/extension/installation/editor-setup.md b/.context/turbostarter-framework-context/sections/extension/installation/editor-setup.md new file mode 100644 index 0000000..935b7a3 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/installation/editor-setup.md @@ -0,0 +1,69 @@ +--- +title: Editor setup +description: Learn how to set up your editor for the fastest development experience. +url: /docs/extension/installation/editor-setup +--- + +# Editor setup + +Of course you can use every IDE you like, but you will have the best possible developer experience with this starter kit when using **VSCode-based** editor with the suggested settings and extensions. + +## Settings + +We have included most recommended settings in the `.vscode/settings.json` file to make your development experience as smooth as possible. It include mostly configs for tools like Prettier, ESLint and Tailwind which are used to enforce some conventions across the codebase. You can adjust them to your needs. + +## LLM rules + +We exposed a special endpoint that will scan all the docs and return the content as a text file which you can use to train your LLM or put in a prompt. You can find it at [/llms.txt](/llms.txt). + +The repository also includes a custom rules for most popular AI editors and agents to ensure that AI completions are working as expected and following our conventions. + +### AGENTS.md + +We've integrated specific rules that help maintain code quality and ensure AI-assisted completions align with our project standards. + +You can find them in the `AGENTS.md` file at the root of the project. This format is a standardized way to instruct AI agents to follow project conventions when generating code and it's used by over **20,000** open-source projects - including [Cursor](https://cursor.sh/), [Aider](https://aider.chat/), [Codex from OpenAI](https://openai.com/blog/openai-codex/), [Jules from Google](https://jules.ai/), [Windsurf](https://windsurf.dev/), and many others. + +```md title="AGENTS.md" +### Code Style and Structure + +- Write concise, technical TypeScript code with accurate examples +- Use functional and declarative programming patterns; avoid classes +- Prefer iteration and modularization over code duplication + +### Naming Conventions + +.... +``` + +To learn more about `AGENTS.md` rules check out the [official documentation](https://agents.md/). + +## Extensions + +Once you cloned the project and opened it in VSCode you should be promted to install suggested extensions which are defined in the `.vscode/extensions.json` automatically. In case you rather want to install them manually you can do so at any time later. + +These are the extensions we recommend: + +### ESLint + +Global extension for static code analysis. It will help you to find and fix problems in your JavaScript code. + + + +### Prettier + +Global extension for code formatting. It will help you to keep your code clean and consistent. + + + +### Pretty TypeScript Errors + +Improves TypeScript error messages shown in the editor. + + + +### Tailwind CSS IntelliSense + +Adds IntelliSense for Tailwind CSS classes to enable autocompletion and linting. + + diff --git a/.context/turbostarter-framework-context/sections/extension/installation/structure.md b/.context/turbostarter-framework-context/sections/extension/installation/structure.md new file mode 100644 index 0000000..a001ec2 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/installation/structure.md @@ -0,0 +1,128 @@ +--- +title: Project structure +description: Learn about the project structure and how to navigate it. +url: /docs/extension/installation/structure +--- + +# Project structure + +The main directories in the project are: + +* `apps` - the location of the main apps +* `packages` - the location of the shared code and the API + +### `apps` Directory + +This is where the apps live. It includes web app (Next.js), mobile app (React Native - Expo), and the browser extension (WXT - Vite + React). Each app has its own directory. + +### `packages` Directory + +This is where the shared code and the API for packages live. It includes the following: + +* shared libraries (database, mailers, cms, billing, etc.) +* shared features (auth, mails, billing, ai etc.) +* UI components (buttons, forms, modals, etc.) + +All apps can use and reuse the API exported from the packages directory. This makes it easy to have one, or many apps in the same codebase, sharing the same code. + +## Repository structure + +By default the monorepo contains the following apps and packages: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +## Browser extension application structure + +The browser extension application is located in the `apps/extension` folder. It contains the following folders: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.context/turbostarter-framework-context/sections/extension/installation/update.md b/.context/turbostarter-framework-context/sections/extension/installation/update.md new file mode 100644 index 0000000..07a5397 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/installation/update.md @@ -0,0 +1,97 @@ +--- +title: Updating codebase +description: Learn how to update your codebase to the latest version. +url: /docs/extension/installation/update +--- + +# Updating codebase + +If you've been following along with our previous guides, you should already have a Git repository set up for your project, with an `upstream` remote pointing to the original repository. + +Updating your project involves fetching the latest changes from the `upstream` remote and merging them into your project. Let's dive into the steps! + + + + ## Stash changes + + + If you don't have any changes to stash, you can skip this step and proceed with the update process. + + Alternatively, you can [commit](https://git-scm.com/docs/git-commit) your changes. + + + If you have any uncommitted changes, stash them before proceeding. It will allow you to avoid any conflicts that may arise during the update process. + + ```bash + git stash + ``` + + This command will save your changes in a temporary location, allowing you to retrieve them later. Once you're done updating, you can apply the stash to your working directory. + + ```bash + git stash apply + ``` + + + + ## Pull changes + + Pull the latest changes from the `upstream` remote. + + ```bash + git pull upstream main + ``` + + When prompted the first time, please opt for merging instead of rebasing. + + Don't forget to run `pnpm i` in case there are any updates in the dependencies. + + + + ## Resolve conflicts + + If there are any conflicts during the merge, Git will notify you. You can resolve them by opening the conflicting files in your code editor and making the necessary changes. + + + If you find conflicts in the `pnpm-lock.yaml file`, accept either of the two changes (avoid manual edits), then run: + + ```bash + pnpm i + ``` + + Your lock file will now reflect both your changes and the updates from the upstream repository. + + + + + ## Run a health check + + After resolving the conflicts, it's time to test your project to ensure everything is working as expected. Run your project locally and navigate through the various features to verify that everything is functioning correctly. + + For a quick health check, you can run: + + ```bash + pnpm lint + pnpm typecheck + + ``` + + If everything looks good, you're all set! Your project is now up to date with the latest changes from the `upstream` repository. + + + + ## Commit and push + + Once everything is working fine, don't forget to commit your changes using: + + ```bash + git commit -m "" + ``` + + and push them to your remote repository with: + + ```bash + git push origin + ``` + + diff --git a/.context/turbostarter-framework-context/sections/extension/internationalization.md b/.context/turbostarter-framework-context/sections/extension/internationalization.md new file mode 100644 index 0000000..c198fa3 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/internationalization.md @@ -0,0 +1,175 @@ +--- +title: Internationalization +description: Learn how to internationalize your extension. +url: /docs/extension/internationalization +--- + +# Internationalization + +Turbostarter's extension uses [i18next](https://www.i18next.com/) and web cookies to store the language preference of the user. This allows the extension to be fully internationalized. + + + We use i18next because it's a robust and widely-adopted internationalization framework that works seamlessly with React. + + The combination with web cookies allows us to persistently store language preferences across all extension contexts and share it with the web app while maintaining excellent performance and browser compatibility. + + +![i18next logo](/images/docs/i18next.jpg) + +## Configuration + +The global configuration is defined in the `@turbostarter/i18n` package and shared across all applications. You can read more about it in the [web configuration](/docs/web/internationalization/configuration) documentation. + +By default, the locale is automatically detected based on the user's device settings. You can override it and set the default locale of your mobile app in the [app configuration](/docs/extension/configuration/app) file. + +Also, the locale configuration is **shared between the web app and the extension** (same as [session](/docs/extension/auth/session)), which means that changing the locale in one place will automatically update it in the other. It's a common pattern for modern apps, simplifying the user experience and reducing the maintenance effort. + +### Cookies + +When a user first opens the [web app](/docs/web), the locale is detected and a cookie is set. This cookie is used to remember the user's language preference. + +You can find its value in the *Cookies* tab of the developer tools of your browser: + +![Locale cookie](/images/docs/extension/locale-cookie.png) + +To enable your extension to read the cookie and that way share the locale settings with the web app, you need to set the cookies permission in the `wxt.config.ts` under `manifest.permissions` field: + +```ts +export default defineConfig({ + manifest: { + permissions: ["cookies"], + }, +}); +``` + +And to be able to read the cookie from your app url, you need to set host\_permissions, which will include your app url: + +```ts +export default defineConfig({ + manifest: { + host_permissions: ["http://localhost/*", "https://your-app-url.com/*"], + }, +}); +``` + +Then you would be able to share the cookie between your apps and also read its value using `browser.cookies` API. + + + Avoid using `` in `host_permissions`. It affects all urls and may cause security issues, as well as a [rejection](https://developer.chrome.com/docs/webstore/review-process#review-time-factors) from the destination store. + + + + + + + + +## Translating extension + +To translate individual components and screens, you can use the well-known `useTranslation` hook. + +```tsx +import { useTranslation } from "@turbostarter/i18n"; + +export const Popup = () => { + const { t } = useTranslation(); + + return
{t("hello")}
; +}; +``` + +That's the recommended way to translate stuff inside your extension. + +### Store presence + +As we saw in the [manifest](/docs/extension/configuration/manifest#locales) section, you can also localize your extension's store presence (like title, description, and other metadata). This allows you to customize how your extension appears in different web stores based on the user's language. + +Each store has specific requirements for localization: + +* [Chrome Web Store](https://developer.chrome.com/docs/webstore/cws-dashboard-listing/) requires a `_locales` directory with JSON files for each language +* [Firefox Add-ons](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/Internationalization) uses a similar structure but with some differences in the manifest +* [Edge Add-ons](https://learn.microsoft.com/en-us/microsoft-edge/extensions/publish/publish-extension#supporting-multiple-languages) uses the same structure as Chrome Web Store + +Although most of the config is abstracted behind common structure, please follow the store-specific guides below for detailed instructions on setting up localization for your extension's store listing. + + + + + + + + + + + +## Language switcher + +TurboStarter ships with a language customizer component that allows users to switch between languages in your extension. You can import and use the `LocaleCustomizer` component in your popup, options page, or any other extension view: + +```tsx +import { LocaleCustomizer } from "@turbostarter/ui-web/i18n"; + +export const Popup = () => { + return ; +}; +``` + + + As the web app and extension share the same i18n configuration (cookie), changing the language in one will affect the other. **This is intentional** and ensures a consistent experience across both platforms, since your extension likely serves as a companion to the web app and should maintain the same language preferences. + + +## Best practices + +Here are key best practices for managing translations in your browser extension: + +* Use descriptive, hierarchical translation keys + + ```ts + // ✅ Good + "popup.settings.language"; + "content.toolbar.save"; + + // ❌ Bad + "saveButton"; + "text1"; + ``` + +* Organize translations by extension views and features + + ``` + _locales/ + ├── en/ + │ ├── messages.json + │ ├── popup.json + │ └── options.json + └── es/ + ├── messages.json + ├── popup.json + └── options.json + ``` + +* Handle fallback languages gracefully + +* Keep manifest descriptions localized for store listings + +* Consider context in translations: + + ```ts + // Context-aware messages + t("button.save", { context: "document" }); // "Save document" + t("button.save", { context: "settings" }); // "Apply changes" + ``` + +* Use placeholders for dynamic content: + + ```ts + // With variables + t("status.saved", { time: "2 minutes ago" }); // "Last saved 2 minutes ago" + + // With plurals + t("items", { count: 5 }); // "5 items" + ``` + +* Keep translations in sync between extension views + +* Cache translations for offline functionality diff --git a/.context/turbostarter-framework-context/sections/extension/marketing.md b/.context/turbostarter-framework-context/sections/extension/marketing.md new file mode 100644 index 0000000..ae44cc8 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/marketing.md @@ -0,0 +1,63 @@ +--- +title: Marketing +description: Learn how to market your mobile application. +url: /docs/extension/marketing +--- + +# Marketing + +As you saw in the [Extras](/docs/extension/extras) section, TurboStarter comes with a lot of tips and tricks to make your product better and help you launch your extension faster with higher traffic. + +The same applies to [submission tips](/docs/extension/extras#submission-tips) to help you get your extension approved by the browser stores faster. + +We'll talk more about the whole process of deploying and publishing your extension in the [Publishing](/docs/extension/publishing/checklist) section, here we'll go through some guidelines that you need to follow to make your store's visibility higher. + +## Before you submit + +To help your extension approval go as smoothly as possible, review the common missteps listed below that can slow down the review process or trigger a rejection. This doesn't replace the official guidelines or guarantee approval, but making sure you can check every item on the list is a good start. + +Make sure you: + +* Test your extension thoroughly for crashes and bugs +* Ensure that all extension information and metadata is complete and accurate +* Update your contact information in case the review team needs to reach you +* Provide clear instructions on how to use your extension, including any special setup required +* If your extension requires an account, provide a demo account or a way to test all features without signing up +* Enable and test all backend services to ensure they're live and accessible during review +* Include detailed explanations of non-obvious features in the extension description +* Ensure your extension complies with the specific browser store's policies (e.g., [Chrome Web Store](https://developer.chrome.com/docs/webstore/program-policies/best-practices), [Firefox Add-ons](https://extensionworkshop.com/documentation/publish/add-on-policies/), [Edge Add-ons](https://learn.microsoft.com/en-us/legal/microsoft-edge/extensions/developer-policies) etc.) +* Remove any references to features not supported in browser extensions (e.g., in-app purchases) + +Following these basic steps during development and before submission will help you get your extension approved faster and with fewer issues. + +## Guidelines + +Each store has slightly different guidelines, but some of them are general and can be applied to all stores: + +* **Security**: Your extension must not contain malicious code or behavior that can harm users' devices or data. +* **Performance**: Your extension must be performant and stable, with a smooth user experience. +* **Privacy**: Your extension must respect user privacy and not collect unnecessary data without explicit consent. +* **Compliance**: Your extension must comply with all relevant laws and regulations. + +You can read more about official guidelines for each store in the following links: + +* [Chrome Web Store](https://developer.chrome.com/docs/webstore/program-policies/best-practices) +* [Firefox Add-ons](https://extensionworkshop.com/documentation/publish/add-on-policies/) +* [Edge Add-ons](https://learn.microsoft.com/en-us/microsoft-edge/extensions/developer-guide/best-practices) + +## Common mistakes + +There are a few common mistakes that you should avoid to make sure your extension can be accepted in the stores. The most common ones are: + +* **Not enough description** - make sure to describe all the features of your extension and how it works in your store listing, that way users won't be confused about what your extension does. Also include detailed information in the single purpose field regarding your extension's primary functionality. +* **Privacy issues** - respect user privacy and require as least permissions as possible, don't ask for permissions that are not necessary for your extension to work +* **Customer support** - provide a way to contact you in case the user has any issues with your extension +* **Stay up-to-date** - keep your extension and its documentation up-to-date to ensure a smooth user experience and to prevent issues during the review process. + + + + + + + + diff --git a/.context/turbostarter-framework-context/sections/extension/monitoring/overview.md b/.context/turbostarter-framework-context/sections/extension/monitoring/overview.md new file mode 100644 index 0000000..b71110a --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/monitoring/overview.md @@ -0,0 +1,143 @@ +--- +title: Overview +description: Get started with browser extension monitoring in TurboStarter. +url: /docs/extension/monitoring/overview +--- + +# Overview + +TurboStarter includes powerful, provider-agnostic monitoring helpers for the browser extension so you can understand **what failed**, **where it failed** (popup, content script, background), and **who it impacted**. The API is intentionally designed for simplicity and extensibility, so you can swap providers without rewriting your extension code. + +## Capturing exceptions + +Extensions have multiple runtimes. To get good coverage, capture errors in the places users actually feel them: + +* **Popup / options UI**: React pages where runtime errors break interactions. +* **Background (service worker)**: long-lived logic like alarms, message routing, and sync. +* **Content scripts**: page integrations where DOM differences and CSP can trigger failures. +* **Manual reporting**: wrap critical flows (auth, billing, webhooks-to-extension sync, imports) with `try/catch` and report with context. + + + + ```tsx + import { captureException } from "@turbostarter/monitoring-extension"; + + export function ExampleButton() { + const onPress = async () => { + try { + /* some risky operation */ + } catch (error) { + captureException(error); + } + }; + + return ; + } + ``` + + + + ```ts + import { captureException } from "@turbostarter/monitoring-extension"; + + browser.runtime.onMessage.addListener((message, _sender, sendResponse) => { + try { + /* handle message */ + sendResponse({ ok: true }); + } catch (error) { + captureException(error); + sendResponse({ ok: false }); + } + }); + ``` + + + + ```ts + import { captureException } from "@turbostarter/monitoring-extension"; + + try { + /* interact with the page DOM */ + } catch (error) { + captureException(error); + } + ``` + + + + + An exception in a content script won't automatically show up in your background logs (and vice versa). Add capture points in each runtime you ship, especially if you do message passing between them. + + +## Identifying users + +Monitoring becomes far more useful once reports can be tied to a stable identity. In extensions you often have two “identities”: + +* **Anonymous, stable install id**: useful before sign-in (and to correlate issues with a device/install). +* **Signed-in user**: once the user authenticates, identify with their user id so issues map to a real account. + +TurboStarter's monitoring layer supports identifying the current user when your auth session resolves. When signed out, pass `null` (or your provider's preferred anonymous identity strategy). + +```tsx title="monitoring.tsx" +import { useEffect } from "react"; +import { identify } from "@turbostarter/monitoring-extension"; +import { authClient } from "~/lib/auth/client"; + +export const MonitoringProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const session = authClient.useSession(); + + useEffect(() => { + if (session.isPending) { + return; + } + + identify(session.data?.user ?? null); + }, [session]); + + return <>{children}; +}; +``` + + + Prefer **stable IDs** over PII. Only attach traits that help debugging (plan, role, extension version) and avoid secrets (tokens, passwords) or sensitive fields unless you've explicitly chosen to send them. + + +## Providers + +The starter supports multiple monitoring providers behind the same API, so you can start with one and switch later. + + + + + + + +## Best practices + + + + Extension issues are often environment-specific. Make sure you can filter by + runtime (popup/background/content script), extension version, and browser. + + + + Focus on crashes and failures that break core flows; skip “expected” states + like validation errors or user cancellations. + + + + Background alarms, retries, and message loops can generate many identical + errors. Guard your capture calls to keep signal high (and costs low). + + + + Don't mix dev/beta/stable releases. Tag builds so you can correlate spikes + with a rollout and verify fixes quickly. + + + +With capture points in each runtime, user identification wired up, and a provider configured, extension monitoring becomes a tight feedback loop: you can spot regressions early, understand which surface area is failing and validate fixes confidently as you ship new versions. diff --git a/.context/turbostarter-framework-context/sections/extension/monitoring/posthog.md b/.context/turbostarter-framework-context/sections/extension/monitoring/posthog.md new file mode 100644 index 0000000..a1c1a8a --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/monitoring/posthog.md @@ -0,0 +1,167 @@ +--- +title: PostHog +description: Learn how to setup PostHog as your browser extension monitoring provider. +url: /docs/extension/monitoring/posthog +--- + +# PostHog + +[PostHog](https://posthog.com/) is a product analytics platform that also supports monitoring capabilities like error tracking and session replay. In extensions, it's especially useful when you want to connect “what broke” with “what the user did” right before the issue occurred. + +TurboStarter keeps monitoring behind a unified API, so you can route exception captures from your popup, background, and content scripts to PostHog without rewriting the call sites. + + + To use PostHog as your monitoring provider, you'll need a PostHog instance. You can use [PostHog Cloud](https://app.posthog.com/signup) or [self-host](https://posthog.com/docs/self-host). + + + + PostHog is also supported as an analytics provider for the extension. If you want to track in-extension events, see the [analytics overview](/docs/extension/analytics/overview) and the [PostHog analytics configuration](/docs/extension/analytics/configuration#posthog). + + +![PostHog banner](/images/docs/web/monitoring/posthog/banner.jpg) + +## Configuration + +Here you'll configure PostHog as the monitoring provider for your extension so exceptions from the popup, background/service worker, and content scripts show up with enough context to debug. + + + + ### Create a project + + Create a PostHog [project](https://app.posthog.com/project/settings) for your extension. You can do this from the [PostHog dashboard](https://app.posthog.com) via the *New Project* action. + + + + ### Activate PostHog as your monitoring provider + + TurboStarter picks the extension monitoring provider through exports in the monitoring package. To route captures to PostHog, export the PostHog implementation from the extension monitoring entrypoint: + + ```ts title="index.ts" + // [!code word:posthog] + export * from "./posthog"; + export * from "./posthog/env"; + ``` + + + + ### Set environment variables + + Add your PostHog project key (and host, if you're not using the default cloud region) to your extension env. Set these locally and in whatever build environment produces your extension bundles: + + ```dotenv title="apps/extension/.env.local" + VITE_POSTHOG_KEY="your-posthog-project-api-key" + VITE_POSTHOG_HOST="https://us.i.posthog.com" + ``` + + + +That's it — load the extension, trigger a test error from the popup/background/content script, and confirm events are arriving in your PostHog project. + +![PostHog error](/images/docs/web/monitoring/posthog/error.png) + +If you want to go beyond basic capture (session replay, feature flags, richer context), follow PostHog's web/extension guidance. + + + + + + + +## Uploading source maps + +**Source maps** map the minified/bundled JavaScript shipped with your extension back to your original source code. Without them, stack traces in PostHog often point at compiled output, which makes debugging much slower. + + + PostHog’s source map flow for web builds relies on injecting metadata into the bundled assets. You must deploy/ship the injected assets, otherwise PostHog can’t match captured errors to the uploaded symbol sets. + + +For extensions built with Vite (which [WXT](https://wxt.dev/) is using under the hood), the high-level flow is: + +* generate `.map` files during the production build +* inject PostHog metadata into the built assets +* upload the injected source maps to PostHog + + + + ### Install the PostHog CLI + + Install the CLI globally: + + ```bash + npm install -g @posthog/cli + ``` + + + + ### Authenticate the CLI + + Authenticate interactively: + + ```bash + posthog-cli login + ``` + + In CI, you can authenticate with environment variables: + + ```dotenv + POSTHOG_CLI_HOST="https://us.posthog.com" + POSTHOG_CLI_ENV_ID="your-posthog-project-id" + POSTHOG_CLI_TOKEN="your-personal-api-key" + ``` + + + + ### Build with source maps enabled + + Make sure your extension build outputs source maps by modifying your `wxt.config.ts` file. + + ```ts title="wxt.config.ts" + import { defineConfig } from "wxt"; + + export default defineConfig({ + /* existing WXT configuration options */ + vite: () => ({ + build: { + sourcemap: "hidden", // [!code ++] Source map generation must be turned on ("hidden", true, etc.) + }, + }), + }); + ``` + + After building, you should have `.js` and `.js.map` files in your output directory. + + + + ### Inject PostHog metadata into the built assets + + Inject release/chunk metadata so PostHog can associate uploaded maps with the shipped bundles: + + ```bash + posthog-cli sourcemap inject --directory ./path/to/assets --project my-extension --version 1.2.3 + ``` + + + + ### Upload source maps + + Upload the injected source maps to PostHog: + + ```bash + posthog-cli sourcemap upload --directory ./path/to/assets + ``` + + + + ### Verify injection and uploads + + After deployment, confirm your production bundles include the injected comment (for example `//# chunkId=...`) and verify symbol sets exist in your PostHog project settings. + + + +With this in place, PostHog can symbolicate extension errors (popup/options UI, background/service worker, and content scripts) so stack traces point back to your original source files. + + + + + + diff --git a/.context/turbostarter-framework-context/sections/extension/monitoring/sentry.md b/.context/turbostarter-framework-context/sections/extension/monitoring/sentry.md new file mode 100644 index 0000000..3c03b98 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/monitoring/sentry.md @@ -0,0 +1,149 @@ +--- +title: Sentry +description: Learn how to setup Sentry as your browser extension monitoring provider. +url: /docs/extension/monitoring/sentry +--- + +# Sentry + +[Sentry](https://sentry.io/) is a popular error monitoring and performance tracking platform. It helps you catch and debug issues by collecting exceptions, stack traces, and helpful context from production. + +For browser extensions, that context matters even more: errors can happen in multiple runtimes (popup/options UI, background/service worker, and content scripts). Sentry makes it easier to see what failed and where it happened so you can ship fixes with confidence. + + + To use Sentry, create an account and a project first. You can sign up [here](https://sentry.io/signup). + + +![Sentry banner](/images/docs/web/monitoring/sentry/banner.png) + +## Configuration + +This section walks you through enabling Sentry for your extension and verifying that errors from the popup, background/service worker, and content scripts are captured reliably. + + + + ### Create a project + + Create a Sentry [project](https://docs.sentry.io/product/projects/) for the extension (JavaScript / browser). You can do this from the Sentry [projects dashboard](https://sentry.io/settings/account/projects/) via the *Create Project* flow. + + + + ### Activate Sentry as your monitoring provider + + TurboStarter picks the extension monitoring provider via exports in the monitoring package. To enable Sentry, export the Sentry implementation from the extension monitoring entrypoint: + + ```ts title="index.ts" + // [!code word:sentry] + export * from "./sentry"; + export * from "./sentry/env"; + ``` + + If you need to customize behavior, the provider implementation lives under `packages/monitoring/extension/src/providers/sentry`. + + + + ### Set environment variables + + From your Sentry project settings, add the DSN and environment to your extension env file (and to any [CI/build step](/docs/extension/publishing/checklist#build-your-app) that produces your extension bundles): + + ```dotenv title="apps/extension/.env.local" + VITE_SENTRY_DSN="your-sentry-dsn" + VITE_SENTRY_ENVIRONMENT="your-project-environment" + ``` + + + +That's it — load the extension, trigger a test error from the popup/background/content script, and confirm it shows up in your [Sentry dashboard](https://sentry.io/settings/account/projects/). + +![Sentry error](/images/docs/web/monitoring/sentry/error.jpg) + +For advanced options (sampling, releases, extra context), refer to [Sentry's JavaScript docs](https://docs.sentry.io/platforms/javascript/). + + + + + + + +## Uploading source maps + +**Source maps** map the bundled/minified JavaScript shipped with your extension back to your original source files. Without them, Sentry stack traces often point to compiled output, which makes debugging across popup/background/content-script runtimes much harder. + + + Generating source maps can expose your source code if `.map` files are publicly accessible. Prefer hidden source maps and/or delete them after upload. + + +Sentry can automatically provide readable stack traces for errors using source maps, requiring a [Sentry auth token](https://docs.sentry.io/account/auth-tokens/). + + + + ### Install the Sentry Vite plugin + + Install the package `@sentry/vite-plugin` in `apps/extension/package.json` as a dev dependency. + + ```bash + pnpm i @sentry/vite-plugin -D --filter extension + ``` + + + + ### Add an auth token for uploads + + Create an [auth token in Sentry](https://docs.sentry.io/account/auth-tokens/) and provide it as an environment variable during builds (locally and in your build environment): + + ```dotenv + SENTRY_AUTH_TOKEN="your-sentry-auth-token" + ``` + + + + ### Enable source maps and configure the plugin + + Enable source map generation in your extension build and add `sentryVitePlugin` **after** your other Vite plugins: + + ```ts title="wxt.config.ts" + import { defineConfig } from "wxt"; + import { sentryVitePlugin } from "@sentry/vite-plugin"; + + export default defineConfig({ + /* existing WXT configuration options */ + vite: () => ({ + build: { + sourcemap: "hidden", // [!code ++] Source map generation must be turned on ("hidden", true, etc.) + }, + plugins: [ + sentryVitePlugin({ + org: "your-sentry-org", + project: "your-sentry-project", + authToken: process.env.SENTRY_AUTH_TOKEN, + + sourcemaps: { + // As you're enabling client source maps, you probably want to delete them after they're uploaded to Sentry. + // Set the appropriate glob pattern for your output folder - some glob examples below: + filesToDeleteAfterUpload: [ + "./**/*.map", + ".*/**/public/**/*.map", + "./dist/**/client/**/*.map", + ], + }, + }), + ], + }), + }); + ``` + + + + ### Verify uploads with a production build + + The Sentry Vite plugin doesn't upload in dev/watch mode. Run a production build, then trigger a test error in the extension and confirm stack traces resolve to your original source. + + + +Once this is in place, errors from your extension's compiled bundles (popup/options UI, background/service worker, content scripts) should show **readable stack traces** in Sentry, without shipping source maps to end users. + + + + + + diff --git a/.context/turbostarter-framework-context/sections/extension/organizations.md b/.context/turbostarter-framework-context/sections/extension/organizations.md new file mode 100644 index 0000000..e448451 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/organizations.md @@ -0,0 +1,63 @@ +--- +title: Organizations/teams +description: Learn how to use organizations/teams/multi-tenancy in TurboStarter extension. +url: /docs/extension/organizations +--- + +# Organizations/teams + +TurboStarter extensions support organizations/teams out of the box by sharing the same authentication session as your web app. The active organization is stored in the session and available to your extension without re-implementing organizations logic. + + + The extension and web app use a single auth session powered by Better Auth. The session includes tenant context (for example, `activeOrganizationId`). When users sign in, switch organizations, or sign out in the web app, the extension picks up these changes automatically. + + Learn more: [Auth → Session](/docs/extension/auth/session). + + +## How it works + +* **No separate auth flow** in the extension. We reuse the web session. +* **Active organization comes from the session** (e.g., `session.activeOrganizationId`). +* **Protected API calls** from the extension include the right cookies, so org‑scoped server logic works as expected. + +![Shared authentication with organizations in extension](/images/docs/extension/organizations.png) + +## Active organization + +Use your existing auth client to read the active organization through the `useActiveOrganization` hook. + +```tsx title="popup.tsx" +import { authClient } from "~/lib/auth"; + +export function Popup() { + const organization = authClient.useActiveOrganization(); + + return <>{organization?.name}; +} +``` + + + If a user switches organizations in the web app, the extension reflects the change through the shared session on the next interaction. For long-lived views, re-read the session or invalidate related queries when appropriate. + + +## Do more with organizations + +Most organization features live in the web app and are exposed via APIs your extension can call. These guides explain the underlying concepts and server behavior your extension builds upon: + + + + + + + + + + + + + + + Looking for the underlying auth setup? Start with [Auth → + Overview](/docs/extension/auth/overview) and [Auth → + Session](/docs/extension/auth/session). + diff --git a/.context/turbostarter-framework-context/sections/extension/publishing/checklist.md b/.context/turbostarter-framework-context/sections/extension/publishing/checklist.md new file mode 100644 index 0000000..590e8fa --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/publishing/checklist.md @@ -0,0 +1,162 @@ +--- +title: Checklist +description: Let's publish your TurboStarter extension to stores! +url: /docs/extension/publishing/checklist +--- + +# Checklist + +When you're ready to publish your TurboStarter extension to stores, follow this checklist. + +This process may take a few hours and some trial and error, so buckle up - you're almost there! + + + + ## Create database instance + + **Why it's necessary?** + + A production-ready database instance is essential for storing your application's data securely and reliably in the cloud. [PostgreSQL](https://www.postgresql.org/) is the recommended database for TurboStarter due to its robustness, features, and wide support. + + **How to do it?** + + You have several options for hosting your PostgreSQL database: + + * [Supabase](/docs/extension/recipes/supabase) - Provides a fully managed Postgres database with additional features + * [Vercel Postgres](https://vercel.com/storage/postgres) - Serverless SQL database optimized for Vercel deployments + * [Neon](https://neon.tech/) - Serverless Postgres with automatic scaling + * [Turso](https://turso.tech/) - Edge database built on libSQL with global replication + * [DigitalOcean](https://www.digitalocean.com/products/managed-databases) - Managed database clusters with automated failover + + Choose a provider based on your needs for: + + * Pricing and budget + * Geographic region availability + * Scaling requirements + * Additional features (backups, monitoring, etc.) + + + + ## Migrate database + + **Why it's necessary?** + + Pushing database migrations ensures that your database schema in the remote database instance is configured to match TurboStarter's requirements. This step is crucial for the application to function correctly. + + **How to do it?** + + You basically have two possibilities for doing a migration: + + + + TurboStarter comes with a predefined GitHub Action to handle database migrations. You can find its definition in the `.github/workflows/publish-db.yml` file. + + What you need to do is set your `DATABASE_URL` as a [secret for your GitHub repository](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions). + + Then, you can run the workflow which will publish the database schema to your remote database instance. + + [Check how to run GitHub Actions workflow.](https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/manually-running-a-workflow) + + + + You can also run your migrations locally, although this is not recommended for production. + + To do so, set the `DATABASE_URL` environment variable to your database URL (that comes from your database provider) in the `.env.local` file and run the following command: + + ```bash + pnpm with-env pnpm --filter @turbostarter/db db:migrate + ``` + + This command will run the migrations and apply them to your remote database. + + [Learn more about database migrations.](/docs/web/database/migrations) + + + + + + ## Set up web backend API + + **Why it's necessary?** + + Setting up the backend is necessary to have a place to store your data and to have other features work properly (e.g. authentication, billing or storage). + + **How to do it?** + + Please refer to the [web deployment checklist](/docs/web/deployment/checklist) on how to set up and deploy the web app backend to production. + + + + ## Environment variables + + **Why it's necessary?** + + Setting the correct environment variables is essential for the extension to function correctly. These variables include API keys, database URLs, and other configuration details required for your extension to connect to various services. + + **How to do it?** + + Use our `.env.example` files to get the correct environment variables for your project. Then add them to your CI/CD provider (e.g. [GitHub Actions](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions)) as a [secret](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions). + + + + ## Build your app + + **Why it's necessary?** + + Building your extension is necessary to create a standalone extension bundle that can be published to the stores. + + **How to do it?** + + You basically have two possibilities to build a bundle for your extension: + + + + TurboStarter comes with a predefined GitHub Action to handle building your extension for submission. You can find its definition in the `.github/workflows/publish-extension.yml` file. + + [Check how to run GitHub Actions workflow.](https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/manually-running-a-workflow) + + This will also save the `.zip` file as an [artifact](https://docs.github.com/en/actions/guides/storing-workflow-data-as-artifacts) of the workflow run, so you can download it from there and submit your extension to stores (if configured). + + + + You can also run your build locally, although this is not recommended for production. + + To do it, run the following command: + + ```bash + pnpm turbo build --filter=extension + ``` + + This will build the extension and package it into a `.zip` file. You can find the output in the `build` folder. + + + + + + ## Submit to stores + + **Why it's necessary?** + + Publishing your extension to the stores is required to make it discoverable and accessible to your users. This is the official distribution channel where users can find, install, and trust your extension. + + **How to do it?** + + We've prepared dedicated guides for each store that TurboStarter supports out-of-the-box, please refer to the following pages: + + + + + + + + + + + +That's it! Your extension is now live and accessible to your users, good job! 🎉 + + + * Optimize your store listing description, keywords, and other relevant information for the stores. + * Remove the placeholder content in the extension or replace it with your own. + * Update the favicon, scheme, store images, and logo with your own branding. + diff --git a/.context/turbostarter-framework-context/sections/extension/publishing/chrome.md b/.context/turbostarter-framework-context/sections/extension/publishing/chrome.md new file mode 100644 index 0000000..433398f --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/publishing/chrome.md @@ -0,0 +1,166 @@ +--- +title: Chrome Web Store +description: Publish your extension to Google Chrome Web Store. +url: /docs/extension/publishing/chrome +--- + +# Chrome Web Store + +[Chrome Web Store](https://chromewebstore.google.com/) is the most popular store for browser extensions, as it makes them available in any Chromium-based browser, including Google Chrome, Edge, Brave, and many others. + +To submit your extension to Chrome Web Store, you'll need to complete a few steps. Here, we'll go through them. + + + Make sure your extension follows the [guidelines](/docs/extension/marketing) and other requirements to increase your chances of getting approved. + + +## Developer account + +Before you can publish items on the Chrome Web Store, you must register as a CWS developer and pay a one-time registration fee. You must provide a developer email when you create your developer account. + +To register, just access the [developer console](https://chrome.google.com/webstore/devconsole). The first time you do this, the following registration screen will appear. First, agree to the developer agreement and policies, then pay the registration fee. + +![Chrome registration fee](/images/docs/extension/chrome/fee.png) + +Once you pay the registration fee and agree to the terms, your account will be created, and you'll be able to proceed to fill out additional information about it. + +![Chrome developer account](/images/docs/extension/chrome/account.png) + +There are a few fields that you'll need to fill in: + +* **Publisher name**: Appears under the title of each of your extensions. If you are a verified publisher, you can display an official publisher URL instead. +* **Verified email**: Verifying your contact email address is required when you set up a new developer account. It's only displayed under your extensions' contact information. Any notifications will be sent to your Chrome Web Store developer account email. +* **Physical address**: Only items that offer functionality to purchase items, additional features, or subscriptions must include a physical address. + + + +## Submission + +After registering your developer account, setting it up, and preparing your extension, you're ready to publish it to the store. + +You can submit your extension in two ways: + +* **Manually**: By uploading your extension's bundle directly to the store. +* **Automatically**: By using GitHub Actions to submit your extension to the stores. + +**The first submission must be done manually, while subsequent updates can be submitted automatically.** We'll go through both approaches. + +### Manual submission + +To manually submit your extension to stores, you will first need to get your extension bundle. If you ran the build step locally, you should already have the `.zip` file in your extension's `build` folder. + +If you used GitHub Actions to build your extension, you can find the results in the workflow run. Download the artifacts and save them on your local machine. + +Then, use the following steps to upload your item: + +1. Go to the [Chrome Web Store Developer Dashboard](https://chrome.google.com/webstore/devconsole/). +2. Sign in to your developer account. +3. Click on the *Add new item* button. +4. Click *Choose file* > *your zip file* > *Upload*. If your item's manifest and other contents are valid, you will see a new item in the dashboard. + +![Chrome extension page](/images/docs/extension/chrome/extension-page.png) + +After you upload the bundle, you'll need to fill in the extension's details, such as the icons, privacy settings, permissions justification, and other information. + +Please refer to the official guides on how to set up your extension's details. + + + + + + + + + +### Automated submission + + + The first submission of your extension to Chrome Web Store must be done manually because you need to provide the store's credentials and extension ID to automation, which will be available only after the first bundle upload. + + +TurboStarter comes with a pre-configured GitHub Actions workflow to submit your extension to web stores automatically. It's located in the `.github/workflows/publish-extension.yml` file. + +What you need to do is fill the environment variables with your store's credentials and extension's details and set them as a [secrets in your Github repository](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions) under correct names: + +```yaml title="publish-extension.yml" +env: + CHROME_EXTENSION_ID: ${{ secrets.CHROME_EXTENSION_ID }} + CHROME_CLIENT_ID: ${{ secrets.CHROME_CLIENT_ID }} + CHROME_CLIENT_SECRET: ${{ secrets.CHROME_CLIENT_SECRET }} + CHROME_REFRESH_TOKEN: ${{ secrets.CHROME_REFRESH_TOKEN }} +``` + +Please refer to the [official guide](https://github.com/PlasmoHQ/bms/blob/main/tokens.md#chrome-web-store-api) to learn how to get these credentials correctly. + +That's it! You can [run the workflow](https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/manually-running-a-workflow) and it will submit your extension to the Chrome Web Store 🎉 + + + This workflow will also try to send your extension to review, but it's not guaranteed to happen. You need to have all required information filled in your extension's details page to make it possible. + + Even then, when you introduce some **breaking change** (e.g. add another permission), you'll need to update your extension store metadata and automatic submit won't be possible. + + To opt out of this behavior (and use only automatic uploading to store, but not sending to review) you can set `--chrome-skip-submit-review` flag in the `publish-extension.yml` file for the `wxt submit` command: + + ```yaml title="publish-extension.yml" + // [!code word:--chrome-skip-submit-review] + - name: 💨 Publish! + run: | + npx wxt submit \ + --chrome-zip apps/extension/build/*-chrome.zip --chrome-skip-submit-review + ``` + + Then, your extension bundle will be uploaded to the store, but you will need to send it to review manually. + + Check out the [official documentation](https://wxt.dev/api/cli/wxt-submit) for more customization options. + + + + + + + + +## Review + +After filling out the information about your item, you are ready to send it to review. Click on *Submit for review* button and confirm that you want to submit your item in the following dialog: + +![Chrome submit for review](/images/docs/extension/chrome/send-to-review.png) + +The confirmation dialog shown above also lets you control the timing of your item's publishing. If you uncheck the checkbox, your item will **not** be published immediately after its review is complete. Instead, you'll be able to manually publish it at a time of your choosing once the review is complete. + +After you submit the item for review, it will undergo a review process. The time for this review depends on the nature of your item. See [Understanding the review process](https://developer.chrome.com/docs/webstore/review-process) for more details. + +There are important emails like take down or rejection notifications that are enabled by default. To receive an email notification when your item is published or staged, you can enable notifications on the *Account page*. + +![Chrome notifications](/images/docs/extension/chrome/notifications.png) + +The review status of your item appears in the [developer dashboard](https://chrome.google.com/webstore/devconsole) next to each item. The status can be one of the following: + +* **Published**: Your item is available to all users. +* **Pending**: Your item is under review. +* **Rejected**: Your item was rejected by the store. +* **Taken Down**: Your item was taken down by the store. + +![Chrome extension status](/images/docs/extension/chrome/review-status.png) + +You'll receive an email notification when the status of your item changes. + + + If your extension has been determined to violate one or more terms or policies, you will receive an email notification that contains the violation description and instructions on how to rectify it. + + If you did not receive an email within a week, check the status of your item. If your item has been rejected, you can see the details on the *Status* tab of your item. + + ![Chrome extension rejected](/images/docs/extension/chrome/rejection.png) + + You'll need to fix the issues and upload a new version of your extension, make sure to follow the [guidelines](/docs/extension/marketing) or check [publishing troubleshooting](/docs/extension/troubleshooting/publishing) for more info. + + If you have been informed about a violation and you do not rectify it, your item will be taken down. See [Violation enforcement](https://developer.chrome.com/docs/webstore/review-process#enforcement) for more details. + + +You can learn more about the review process in the official guides listed below. + + + + + + diff --git a/.context/turbostarter-framework-context/sections/extension/publishing/edge.md b/.context/turbostarter-framework-context/sections/extension/publishing/edge.md new file mode 100644 index 0000000..939ddeb --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/publishing/edge.md @@ -0,0 +1,242 @@ +--- +title: Edge Add-ons +description: Publish your extension to Microsoft Edge Add-ons. +url: /docs/extension/publishing/edge +--- + +# Edge Add-ons + +[Microsoft Edge Add-ons](https://microsoftedge.microsoft.com/addons/) distributes extensions to Microsoft Edge users. If you already have a Chromium-based extension, you can submit it to Edge with minimal changes. + +This guide walks you through manual submission and optional automation, aligned with the official process. + + + Make sure your extension follows the general [guidelines](/docs/extension/marketing) and the Edge Add-ons developer policies to increase your chances of approval. + + +## Developer account + +To enroll in the Microsoft Edge program you need to have a Microsoft account. If you don't have one, you can create one [here](https://account.microsoft.com/account/signup?signin=1\&ru=https://account.microsoft.com/account/login?loginMethod=email). + +![Microsoft account](/images/docs/extension/edge/create-microsoft-account.png) + +Next, before you can publish your extension to Edge Add-ons, you need to register your developer account in [Partner Center](https://partner.microsoft.com/dashboard/microsoftedge/public/login?ref=dd). Fill out the required fields and submit the form with *Finish* button. Wait for the email that your account has been verified - you're ready to submit your extension! + +![Partner Center](/images/docs/extension/edge/developer-account.png) + + + +## Submission + +After your account is ready and the extension bundle is prepared, you can publish it. There are two paths: + +* **Manually**: Upload your `.zip` package through Partner Center. +* **Automatically**: Use CI to upload new versions after the first manual submission. + +**The first submission should be done manually.** Subsequent updates can be automated once you have your extension ID and required credentials. + +### Manual submission + +To manually submit your extension to stores, you will first need to get your extension bundle. If you ran the build step locally, you should already have the .zip file in your extension's build folder. + +If you used GitHub Actions to build your extension, you can find the results in the workflow run. Download the artifacts and save them on your local machine. + +Then, use the following steps to upload your item: + + + + #### Sign in to your developer account + + Go to the [Partner Center](https://partner.microsoft.com/dashboard/microsoftedge/public/login?ref=dd) and sign in to your developer account. + + + + #### Create new extension + + Click the *Create new extension* button to start a new submission. + + ![Create new extension](/images/docs/extension/edge/create.png) + + + + #### Upload the extension package + + The *Extension overview* page shows information for a specific extension: + + ![Extension overview](/images/docs/extension/edge/upload.png) + + To upload your extension package: + + 1. Click *Packages* in the left sidebar. + 2. Drag and drop your `.zip` file or click *Browse your files* to select it. + 3. Wait for validation to complete. If it fails, fix any issues and re-upload. + 4. Review the extracted extension details and click *Continue*. + + + + #### Set availability + + Choose visibility: + + * `Public`: discoverable in the store and via search. + * `Hidden`: not discoverable; accessible via direct listing URL only. + + Select markets where the extension is available. You can later add or remove markets; existing users retain access to installed versions. + + ![Availability](/images/docs/extension/edge/availability.png) + + + + #### Enter properties + + Provide category, privacy policy requirements, privacy policy URL (if applicable), website URL, and support contact. + + These are shown to users on the listing and must meet policy requirements. + + ![Properties](/images/docs/extension/edge/properties.png) + + Follow the [official documentation](https://learn.microsoft.com/en-us/microsoft-edge/extensions/publish/publish-extension#step-4-enter-properties-describing-your-extension) for more details. + + + + #### Add store listing details + + Fill in the store listing details for your extension: + + * **Display name**: The name shown in the store (from your manifest file). + * **Description**: A detailed description (250-5000 characters) explaining what your extension does and why users should install it. + * **Extension Store logo**: A 300x300 pixel logo representing your extension. + * **Screenshots**: Up to 10 screenshots (640x480 or 1280x800 pixels) showing your extension's functionality. + * **Small/Large promotional tiles**: Optional promotional images for store featuring. + * **YouTube video URL**: Optional promotional video. + * **Search terms**: Keywords to help users discover your extension (up to 21 words total). + + You must provide the description and logo for each supported language. Other fields are optional but recommended for better discoverability. + + ![Store listing details](/images/docs/extension/edge/store-listing.png) + + Follow the [official documentation](https://learn.microsoft.com/en-us/microsoft-edge/extensions/publish/publish-extension#step-5-add-store-listing-details-for-your-extension) for detailed requirements and best practices. + + + + #### Submit for review + + Complete the submission by providing testing notes to help certification testers understand your extension. + + Click the *Submit* button to open the submission page: + + ![Submit extension](/images/docs/extension/edge/submit.png) + + In the **Notes for certification** text box, provide additional information to help testers properly evaluate your extension. Include any relevant details such as: + + * Test account usernames and passwords + * Steps to access hidden or locked features + * Expected differences based on region or user settings + * Information about changes if this is an update + * Any other context testers need to understand your submission + + Once you've added your notes, click the *Publish* button to submit your extension for certification. + + Your extension will proceed to the certification step, which can take up to seven business days. + + After passing certification, your extension will be published to [Microsoft Edge Add-ons](https://microsoftedge.microsoft.com/addons/) and the status in Partner Center will change to "In the Store". + + + + + + + + + +## Automated submission + + + The first submission of your extension to Microsoft Edge Add-ons must be done manually because you need to provide the store's credentials and extension ID to automation, which will be available only after the first bundle upload. + + +TurboStarter comes with a pre-configured GitHub Actions workflow to submit your extension to web stores automatically. It's located in the .github/workflows/publish-extension.yml file. + +What you need to do is fill the environment variables with your store's credentials and extension's details and set them as a [secrets in your Github repository](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions) under correct names: + +```yaml title="publish-extension.yml" +env: + EDGE_PRODUCT_ID: ${{ secrets.EDGE_PRODUCT_ID }} + EDGE_CLIENT_ID: ${{ secrets.EDGE_CLIENT_ID }} + EDGE_API_KEY: ${{ secrets.EDGE_API_KEY }} +``` + +Please refer to the [official guide](https://github.com/PlasmoHQ/bms/blob/main/tokens.md#edge-add-ons-api-v11) to learn how to get these credentials correctly. + +Once configured, you can manually [trigger the workflow](https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/manually-running-a-workflow) to upload the new version to Edge Add-ons 🎉 + + + This workflow will also try to send your extension to review, but it's not guaranteed to happen. You need to have all required information filled in your extension's details page to make it possible. + + Even then, when you introduce some **breaking change** (e.g. add another permission), you'll need to update your extension store metadata and automatic submit won't be possible. + + To opt out of this behavior (and use only automatic uploading to store, but not sending to review) you can set `--edge-skip-submit-review` flag in the `publish-extension.yml` file for the `wxt submit` command: + + ```yaml title="publish-extension.yml" + // [!code word:--edge-skip-submit-review] + - name: 💨 Publish! + run: | + npx wxt submit \ + --edge-zip apps/extension/build/*-chrome.zip --edge-skip-submit-review + ``` + + Then, your extension bundle will be uploaded to the store, but you will need to send it to review manually. + + Check out the [official documentation](https://wxt.dev/api/cli/wxt-submit) for more customization options. + + + + + + + + +## Review + +After you submit your extension, it enters Microsoft's certification and publishing pipeline. + +1. Preprocessing + * Uploaded packages are queued and scanned. If errors are detected during preprocessing, you'll see a message and must resolve issues before re-uploading. +2. Certification + * Security tests: packages are checked for viruses and malware. + * Content compliance: human review of your listing and content for policy adherence. +3. Release and publishing + * If you selected publish immediately, publishing begins right away; otherwise schedule/hold options apply. + * While publishing, the submission status page shows rollout details. When complete, the status changes from "Publishing" to "In the Store". +4. Edge Add-ons curation and ranking + * Discovery is influenced by quality, relevancy (name, description, popularity, user experience), and popularity (ratings and averages). Security and policy compliance are verified per the developer policies. + +Microsoft may also perform spot checks after publishing to ensure ongoing compliance. + +The review status of your item appears in the [Partner Center](https://partner.microsoft.com/dashboard/microsoftedge/public/login?ref=dd) under the *Overview* page of your item. + +![Edge extension review status](/images/docs/extension/edge/review-status.png) + +You'll receive an email notification when the status of your item changes. + + + If your extension has been determined to violate one or more terms or policies, you will receive an email notification that contains the violation description and instructions on how to rectify it. + + ![Rejection email](/images/docs/extension/edge/rejection-email.png) + + You can also check the reason behind the rejection on the *Certification report* page of your item. + + ![Certification report](/images/docs/extension/edge/certification-report.png) + + You'll need to fix the issues and upload a new version of your extension. Make sure to follow the [guidelines](/docs/extension/marketing) or check [publishing troubleshooting](/docs/extension/troubleshooting/publishing) for more info. + + +You can learn more about the review process in the official guides listed below. + + + + + + + + diff --git a/.context/turbostarter-framework-context/sections/extension/publishing/firefox.md b/.context/turbostarter-framework-context/sections/extension/publishing/firefox.md new file mode 100644 index 0000000..1b23aba --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/publishing/firefox.md @@ -0,0 +1,218 @@ +--- +title: Firefox Add-ons +description: Publish your extension to Mozilla Firefox Add-ons. +url: /docs/extension/publishing/firefox +--- + +# Firefox Add-ons + +Mozilla Firefox doesn't share extensions with [Google Chrome](/docs/extension/publishing/chrome), so you'll need to publish your extension to it separately. + +Here, we'll go through the process of publishing an extension to [Firefox Add-ons](https://addons.mozilla.org/). + + + Make sure your extension follows the [guidelines](/docs/extension/marketing) and other requirements to increase your chances of getting approved. + + +## Developer account + +Before you can publish items on Firefox Add-ons, you must register a developer account. In comparison to the Chrome Web Store, Firefox Add-ons doesn't require a registration fee. + +To register, go to [addons.mozilla.org](https://addons.mozilla.org/) and click on the *Register* button. + +![Mozilla registration](/images/docs/extension/firefox/portal.png) + +It's important to set at least a display name on your profile to increase transparency with users, add-on reviewers, and the greater community. + +You can do it in the *Edit My Profile* section: + +![Mozilla profile](/images/docs/extension/firefox/profile.png) + + + +## Submission + +After registering your developer account, setting it up, and preparing your extension, you're ready to publish it to the store. + +You can submit your extension in two ways: + +* **Manually**: By uploading your extension's bundle directly to the store. +* **Automatically**: By using GitHub Actions to submit your extension to the stores. + +**The first submission must be done manually, while subsequent updates can be submitted automatically.** We'll go through both approaches. + +### Manual submission + +To manually submit your extension to stores, you will first need to get your extension bundle. If you ran the build step locally, you should already have the `.zip` file in your extension's `build` folder. + +If you used GitHub Actions to build your extension, you can find the results in the workflow run. Download the artifacts and save them on your local machine. + +Then, use the following steps to upload your item: + + + + #### Sign in to your developer account + + Go to the [Add-ons Developer Hub](https://addons.mozilla.org/developers/) and sign in to your developer account. + + + + #### Choose distribution method + + You should reach the following page: + + ![Mozilla distribution](/images/docs/extension/firefox/distribution.png) + + Here, you have two ways of distributing your extension: + + * **On this site**, if you want your add-on listed on AMO (Add-ons Manager). + * **On your own**, if you plan to distribute the add-on yourself and don't want it listed on AMO. + + We recommend going with the first option, as it will allow you to reach more users and get more feedback. If you decide to go with the second option, please refer to the [official documentation](https://extensionworkshop.com/documentation/publish/self-distribution/) for more details. + + + + #### Submit your extension + + On the next page, click on *Select file* and choose your extension's `.zip` bundle. + + ![Mozilla upload](/images/docs/extension/firefox/upload.png) + + Once you upload the bundle, the validator checks the add-on for issues and the page updates: + + ![Mozilla validation](/images/docs/extension/firefox/validation.png) + + If your add-on passes all the checks, you can proceed to the next step. + + + You may receive a message that you only have warnings. It's advisable to address these warnings, particularly those flagged as security or privacy issues, as they may result in your add-on failing review. However, **you can continue with the submission**. + + + If the validation fails, you'll need to address the issues and upload a new version of your add-on. + + + + #### Submit source code (if needed) + + You'll need to indicate whether you need to provide the source code of your extension: + + ![Mozilla source code](/images/docs/extension/firefox/source-code.png) + + If you select *Yes*, a section displays describing what you need to submit. Click *Browse* and locate and upload your source code package. See [Source code submission](https://extensionworkshop.com/documentation/publish/source-code-submission/) for more information. + + + You may receive a message that you only have warnings. It's advisable to address these warnings, particularly those flagged as security or privacy issues, as they may result in your add-on failing review. However, **you can continue with the submission**. + + + If the validation fails, you'll need to address the issues and upload a new version of your add-on. + + + + #### Add metadata + + On the next page, you'll need to provide the following additional information about your extension: + + ![Mozilla additional information](/images/docs/extension/firefox/additional-info.png) + + * **Name**: Your add-on's name. + * **Add-on URL**: The URL for your add-on on AMO. A URL is automatically assigned based on your add-on's name. To change this, click Edit. The URL must be unique. You will be warned if another add-on is using your chosen URL, and you must enter a different one. + * **Summary**: A useful and descriptive short summary of your add-on. + * **Description**: A longer description that provides users with details of the extension's features and functionality. + * **This add-on is experimental**: Indicate if your add-on is experimental or otherwise not ready for general use. The add-on will be listed but with reduced visibility. You can remove this flag when your add-on is ready for general use. + * **This add-on requires payment, non-free services or software, or additional hardware**: Indicate if your add-on requires users to make an additional purchase for it to work fully. + * **Select up to 2 Firefox categories for this add-on**: Select categories that describe your add-on. + * **Select up to 2 Firefox for Android categories for this add-on**: Select categories that describe your add-on. + * **Support email and Support website**: Provide an email address and website where users can get in touch when they have questions, issues, or compliments. + * **License**: Select the appropriate license for your add-on. Click Details to learn more about each license. + * **This add-on has a privacy policy**: If any data is being transmitted from the user's device, a privacy policy explaining what is being sent and how it's used is required. Check this box and provide the privacy policy. + * **Notes for Reviewers**: Provide information to assist the AMO reviewer, such as login details for a dummy account, source code information, or similar. + + + + #### Finalize the process + + Once you're ready, click on the *Submit Version* button. + + ![Mozilla submit](/images/docs/extension/firefox/submit.png) + + You can still edit your add-on's details from the dedicated page after submission. + + + + + + + + + +### Automated submission + + + The first submission of your extension to Firefox Add-ons must be done manually because you need to provide the store's credentials and extension ID to automation, which will be available only after the first bundle upload. + + +TurboStarter comes with a pre-configured GitHub Actions workflow to submit your extension to web stores automatically. It's located in the `.github/workflows/publish-extension.yml` file. + +What you need to do is fill the environment variables with your store's credentials and extension's details and set them as a [secrets in your Github repository](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions) under correct names: + +```yaml title="publish-extension.yml" +env: + FIREFOX_EXTENSION_ID: ${{ secrets.FIREFOX_EXTENSION_ID }} + FIREFOX_JWT_ISSUER: ${{ secrets.FIREFOX_JWT_ISSUER }} + FIREFOX_JWT_SECRET: ${{ secrets.FIREFOX_JWT_SECRET }} +``` + +Please refer to the [official guide](https://github.com/PlasmoHQ/bms/blob/main/tokens.md#firefox-add-ons-api) to learn how to get these credentials correctly. + +That's it! You can [run the workflow](https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/manually-running-a-workflow) and it will submit your extension to the Firefox Add-ons 🎉 + + + This workflow will also try to send your extension to review, but it's not guaranteed to happen. You need to have all required information filled in your extension's details page to make it possible. + + Even then, when you introduce some **breaking change** (e.g., add another permission), you'll need to update your extension store metadata and automatic submission won't be possible. + + + + + + + + +## Review + +Once you submit your extension bundle, it's automatically sent to review and will undergo a review process. The time for this review depends on the nature of your item. + +The add-on review process includes the following phases: + +1. **Automatic Review**: Upon upload, the add-on undergoes several automatic validation steps to ensure its general safety. +2. **Content Review**: Shortly after submission, a human reviewer inspects the add-on to ensure that the listing adheres to content review guidelines, including metadata such as the add-on name and description. +3. **Technical Code Review**: The add-on's source code is examined to ensure compliance with review policies. +4. **Basic Functionality Testing**: After the source code is verified as safe, the add-on undergoes basic functionality testing to confirm it operates as described. + +There are important emails like takedown or rejection notifications that are enabled by default. To receive an email notification when your item is published or staged, you can enable notifications in the *Account Settings*. + +![Mozilla notifications](/images/docs/extension/firefox/notifications.png) + +The review status of your item appears in the [developer hub](https://addons.mozilla.org/en-US/firefox/) next to each item. + +![Mozilla review status](/images/docs/extension/firefox/review-status.png) + +You'll receive an email notification when the status of your item changes. + + + If your extension has been determined to violate one or more terms or policies, you will receive an email notification that contains the violation description and instructions on how to rectify it. + + You can also check the reason behind the rejection on the *Status* page of your item. + + ![Mozilla extension rejected](/images/docs/extension/firefox/rejection.png) + + You'll need to fix the issues and upload a new version of your extension. Make sure to follow the [guidelines](/docs/extension/marketing) or check [publishing troubleshooting](/docs/extension/troubleshooting/publishing) for more info. + + +You can learn more about the review process in the official guides listed below. + + + + + + diff --git a/.context/turbostarter-framework-context/sections/extension/publishing/updates.md b/.context/turbostarter-framework-context/sections/extension/publishing/updates.md new file mode 100644 index 0000000..e12244c --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/publishing/updates.md @@ -0,0 +1,28 @@ +--- +title: Updates +description: Learn how to update your published extension. +url: /docs/extension/publishing/updates +--- + +# Updates + +After publishing your extension to the stores, you can release updates to deliver new features and bug fixes to your users. + +TurboStarter provides a ready-to-use process for updating your extensions. Let's quickly review how it works. + +## Uploading a new version + +The recommended way to update your extension is to submit a new version to the stores. This method is the most reliable, although it may take some time for the new version to be approved and become available to users. + +To submit a new version, simply update the version number in your `package.json` file: + +```json title="package.json" +{ + ... + "version": "1.0.0", // [!code --] + "version": "1.0.1", // [!code ++] + ... +} +``` + +Next, follow the exact same steps as [when you initially published your extension](/docs/extension/publishing/checklist). When submitting your extension for review, be sure to provide release notes describing the new version. diff --git a/.context/turbostarter-framework-context/sections/extension/recipes/supabase.md b/.context/turbostarter-framework-context/sections/extension/recipes/supabase.md new file mode 100644 index 0000000..cd8bbc2 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/recipes/supabase.md @@ -0,0 +1,221 @@ +--- +title: Supabase +description: Learn how to set up Supabase as the database (and optional storage) provider for your TurboStarter project. +url: /docs/extension/recipes/supabase +--- + +# Supabase + +[Supabase](https://supabase.com) is an open-source backend platform built on top of PostgreSQL that provides a managed database, storage, and other features out of the box. + +You can adopt Supabase incrementally - start with just the pieces you need (for example, database only, or database + storage) and add more features over time. There's no requirement to integrate everything at once. + +In this guide, we'll walk you through the process of setting up Supabase as a provider for your TurboStarter project. This could include using it as a [database](https://supabase.com/docs/guides/database), [storage](https://supabase.com/docs/guides/storage), [edge runtime for your API](https://supabase.com/docs/guides/functions) and more. + +## Prerequisites + +Before you start, make sure you have: + +* **TurboStarter project** cloned locally with dependencies installed (you can use our [CLI](/docs/web/cli) to create a new project in seconds) +* **Supabase account** - you can create one at [supabase.com](https://supabase.com/sign-up) +* Basic familiarity with the core database docs: + * [Database overview](/docs/web/database/overview) + * [Migrations](/docs/web/database/migrations) + * [Database client](/docs/web/database/client) + + + + ## Create a new Supabase project + + 1. Go to the [Supabase dashboard](https://supabase.com). + 2. Create a **new project** (choose a strong database password and a region close to your users). + 3. Supabase will automatically provision a **PostgreSQL database** for you. + + ![Create a new Supabase project](/images/docs/web/recipes/supabase/create-project.png) + + Optionally, you can customize the **Security options** by choosing the **Only Connection String** option - it will opt out of autogenerating API for tables inside your database. It's not needed for TurboStarter setup, but of course you can still leverage it for your custom use-cases. + + ![Security options](/images/docs/web/recipes/supabase/security-options.png) + + Once the project is ready, you can fetch the connection string. + + + + ## Get the database connection string + + In the Supabase dashboard: + + 1. Open your project. + 2. Click on the **Connect** button at the top. + 3. Locate the **connection string** for your chosen ORM (it will be under the **ORMs** tab). + + ![Connect application](/images/docs/web/recipes/supabase/connect-app.png) + + Copy this value - you'll use it as your `DATABASE_URL`. + + + In your Supabase connection string, you can see a placeholder like `[YOUR-PASSWORD]`. Make sure to replace this with the actual password you set when creating your Supabase project. + + + + + ## Configure environment variables + + TurboStarter reads database connection settings from the **root** `.env.local` file and uses them inside the `@turbostarter/db` package. + + Create (or update) the `.env.local` file in the **monorepo root**: + + ```dotenv title=".env.local" + DATABASE_URL="postgres://postgres.[YOUR-PROJECT-REF]:[YOUR-PASSWORD]@aws-0-[aws-region].pooler.supabase.com:6543/postgres?pgbouncer=true&connection_limit=1" + ``` + + Replace: + + * `YOUR-PROJECT-REF` with your Supabase project ref + * `YOUR-PASSWORD` with the database password you set when creating the project + * `aws-region` with the region shown in the Supabase connection string + + + These variables are validated in the `@turbostarter/db` package and used to create Drizzle client for your database. + + + For more background on how `DATABASE_URL` is used, see [Database overview](/docs/web/database/overview). + + + + ## Setup your Supabase database + + With `DATABASE_URL` now pointing to Supabase, you can apply the existing TurboStarter schema to your Supabase database. + + From the monorepo root, run: + + ```bash + pnpm with-env pnpm --filter @turbostarter/db db:migrate + ``` + + This will: + + * Use your Supabase `DATABASE_URL` from `.env.local` + * Run all pending SQL migrations from `packages/db/migrations` + * Create the full TurboStarter schema (users, billing, demo tables, etc.) in Supabase + + If you're actively iterating on the schema, you can generate new migrations and apply them as described in [Migrations](/docs/web/database/migrations). + + + After running your migrations, you may want to seed your database with initial data (such as demo users or organizations). You can do this by running the following command: + + ```bash + pnpm with-env pnpm turbo db:seed + ``` + + This will populate your Supabase database with some example data you can use to test your application. + + + + + ## Use Supabase Storage as S3-compatible storage + + TurboStarter's storage layer is designed to work seamlessly with **any S3-compatible provider**. In this section, we'll show how to use [Supabase Storage](/docs/web/storage/overview) as your application's file storage back-end. + + Supabase Storage provides a simple, S3-compatible API and is a great choice if you're already using Supabase for your database. + + ### Create a storage bucket + + 1. In the Supabase dashboard, go to **Storage → Buckets**. + 2. Click **Create bucket** (name it whatever you want, for example `avatars` or `uploads`). + 3. Adjust settings based on your needs (e.g. limit the maximum file size, specify the allowed file types, etc.) + + ![Create a new bucket](/images/docs/web/recipes/supabase/create-bucket.png) + + You can create multiple buckets (for documents, images, videos, etc.) if needed. + + ### Generate S3 access keys in Supabase dashboard + + 1. Go to **Storage → S3 → Access keys**. + 2. Click **New access key**. + 3. Give it a descriptive name and create the key. + 4. Copy the **Access key ID** and **Secret access key** to use in your application. + + ![Generate S3 access keys](/images/docs/web/recipes/supabase/s3-keys.png) + + ### Configure S3 environment variables for Supabase Storage + + In your weba application's `.env.local`, add (or update) the S3 configuration used by TurboStarter's storage layer: + + ```dotenv title=".env.local" + S3_REGION="us-east-1" + S3_BUCKET="avatars" + S3_ENDPOINT="https://[YOUR-PROJECT-REF].supabase.co/storage/v1/s3" + S3_ACCESS_KEY_ID="your-access-key-id" + S3_SECRET_ACCESS_KEY="your-secret-access-key" + ``` + + These variables integrate directly with the storage configuration described in: + + * [Storage overview](/docs/web/storage/overview) + * [Storage configuration](/docs/web/storage/configuration) + + Once set, existing TurboStarter file upload flows (e.g. user avatars, organization logos) will use Supabase Storage via presigned URLs. + + + + ## Run your API on Supabase Edge Functions + + As we're using a [Hono](https://hono.dev) as our API server, you can deploy it as a Supabase Edge Function so it runs close to your users. + + At a high level: + + 1. Install the [Supabase CLI](https://supabase.com/docs/guides/cli) and initialize a Supabase project locally with `supabase init`. + 2. Create a new [Edge Function](https://supabase.com/docs/guides/functions/quickstart) (for example `hono-backend`) with `supabase functions new hono-backend`. + 3. Inside the generated function (for example `supabase/functions/hono-backend/index.ts`), set up a basic Hono app and export it via `Deno.serve(app.fetch)`: + + ```ts + import { Hono } from "jsr:@hono/hono"; + + // change this to your function name + const functionName = "hono-backend"; + const app = new Hono().basePath(`/${functionName}`); + + app.get("/hello", (c) => c.text("Hello from hono-server!")); + + Deno.serve(app.fetch); + ``` + + 4. Run the function locally with `supabase start` and `supabase functions serve --no-verify-jwt`, then call it from your TurboStarter app using the local or deployed function URL. + 5. When you're ready, deploy the function with `supabase functions deploy` (or `supabase functions deploy hono-backend`) and manage it using the Supabase dashboard, as described in the [Supabase Edge Functions docs](https://supabase.com/docs/guides/functions). + + This is entirely optional, but it's a great fit for lightweight APIs, webhooks, and other serverless logic you want to run alongside your Supabase project. + + + + ## Explore additional Supabase features + + Supabase is a full Postgres development platform, so beyond the database and storage pieces wired up above you can gradually add more features as your app grows ([see the Supabase homepage](https://supabase.com/) for an overview). + + Some features that fit especially well with TurboStarter's design are: + + * [Realtime](https://supabase.com/docs/guides/realtime) - built on [Postgres replication](https://www.postgresql.org/docs/current/runtime-config-replication.html), so you can stream changes from your existing TurboStarter tables (inserts, updates, deletes) into live UIs without changing how you manage schema or RLS. You still define tables and policies via `@turbostarter/db`, and opt into Realtime on top. + * [Vector](https://supabase.com/docs/guides/vector) - powered by the [pgvector](https://github.com/pgvector/pgvector) extension and stored in regular Postgres tables, making it easy to integrate semantic search or AI features while keeping everything in the same migrations and Drizzle models you already use in TurboStarter. We're using it extensively in our dedicated [AI Kit](/ai). + * [Cron](https://supabase.com/docs/guides/functions/cron) - enables you to schedule background jobs and periodic tasks with [pg\_cron](https://github.com/citusdata/pg_cron). You can define cron jobs for things like scheduled database cleanups, sending emails, report generation, or any recurring logic, all managed alongside your TurboStarter app with full Postgres integration. + + Because these features are all layered on top of Postgres, you can introduce them incrementally and keep managing everything through your familiar workflow. + + + + ## Start the development server + + With the database and other services configured to use Supabase, you can start TurboStarter as usual from the monorepo root: + + ```bash + pnpm dev + ``` + + TurboStarter will now: + + * Use **Supabase Postgres** as your database through `DATABASE_URL` + * Use **Supabase Storage** as your file storage through the S3-compatible endpoint + * Leverage **Supabase Edge Functions** (for example, with Hono) for your serverless backend + + + +That's it! You can now start building your application with Supabase as your main provider. Explore the [Supabase documentation](https://supabase.com/docs) for more features and best practices. diff --git a/.context/turbostarter-framework-context/sections/extension/stack.md b/.context/turbostarter-framework-context/sections/extension/stack.md new file mode 100644 index 0000000..c13d61a --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/stack.md @@ -0,0 +1,82 @@ +--- +title: Tech Stack +description: A detailed look at the technical details. +url: /docs/extension/stack +--- + +# Tech Stack + +## Turborepo + +[Turborepo](https://turbo.build/) is a monorepo tool that helps you manage your project's dependencies and scripts. We chose a monorepo setup to make it easier to manage the structure of different features and enable code sharing between different packages. + +} /> + +## WXT (Vite) + +> It's like Next.js for browser extensions. + +[WXT](https://www.wxt.dev/) is a very lightweight and powerful framework (based on [Vite](https://vite.dev/)) for building browser extensions using most popular frontend tools. It provides a modern development experience with features like hot module reloading, TypeScript support, and automatic manifest generation. + +WXT simplifies the process of creating cross-browser extensions, allowing you to focus on your extension's functionality rather than boilerplate setup. + + + } /> + + } /> + + +## React + +[React](https://reactjs.org/) is a JavaScript library for building user interfaces. It's the core technology we use for creating the UI of our browser extension, allowing for efficient updates and rendering of components. + +} /> + +## Tailwind CSS + +[Tailwind CSS](https://tailwindcss.com) is a utility-first CSS framework that helps you build custom designs without writing any CSS. We also use [Radix UI](https://radix-ui.com) for our headless components library and [shadcn/ui](https://ui.shadcn.com) which enables you to generate pre-designed components with a single command. + + + } /> + + } /> + + } /> + + +## Hono & React Query + +[Hono](https://hono.dev) is a small, simple, and ultrafast web framework for the edge. It provides tools to help you build APIs and web applications faster. It includes an RPC client for making type-safe function calls from the frontend. We use Hono to build our serverless API endpoints. + +To make data fetching and caching from our API easy and reliable, we pair Hono with [React Query](https://tanstack.com/query/latest). It helps manage asynchronous data, caching, and state synchronization between the client and backend, delivering a fast and seamless UX. + + + } /> + + + } + /> + + +## Better Auth + +[Better Auth](https://www.better-auth.com) is a modern authentication library for fullstack applications. It provides ready-to-use snippets for features like email/password login, magic links, OAuth providers, and more. We use Better Auth to handle all authentication flows in our application. + +} /> + +## Drizzle + +[Drizzle](https://orm.drizzle.team/) is a super fast [ORM](https://orm.drizzle.team/docs/overview) (Object-Relational Mapping) tool for databases. It helps manage databases, generate TypeScript types from your schema, and run queries in a fully type-safe way. + +We use [PostgreSQL](https://www.postgresql.org) as our default database, but thanks to Drizzle's flexibility, you can easily switch to MySQL, SQLite or any [other supported database](https://orm.drizzle.team/docs/connect-overview) by updating a few configuration lines. + + + } /> + + } /> + diff --git a/.context/turbostarter-framework-context/sections/extension/structure/background.md b/.context/turbostarter-framework-context/sections/extension/structure/background.md new file mode 100644 index 0000000..7617ba1 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/structure/background.md @@ -0,0 +1,63 @@ +--- +title: Background service worker +description: Configure your extension's background service worker. +url: /docs/extension/structure/background +--- + +# Background service worker + +An extension's service worker is a powerful script that runs in the background, separate from other parts of the extension. It's loaded when it is needed, and unloaded when it goes dormant. + +Once loaded, an extension service worker generally runs as long as it is actively receiving events, though it [can shut down](https://developer.chrome.com/docs/extensions/develop/concepts/service-workers/lifecycle#idle-shutdown). Like its web counterpart, an extension service worker cannot access the DOM, though you can use it if needed with [offscreen documents](https://developer.chrome.com/docs/extensions/reference/api/offscreen). + +Extension service workers are more than network proxies (as web service workers are often described), they run in a separate [service worker context](https://developer.mozilla.org/en-US/docs/Web/API/Service_Worker_API/Using_Service_Workers). For example, when in this context, you no longer need to worry about CORS and can fetch resources from any origin. + +In addition to the [standard service worker events](https://developer.mozilla.org/docs/Web/API/ServiceWorkerGlobalScope#events), they also respond to extension events such as navigating to a new page, clicking a notification, or closing a tab. They're also registered and updated differently from web service workers. + +**It's common to offload heavy computation to the background service worker**, so you should always try to do resouce-expensive operations there and send results using [Messages API](/docs/extension/structure/messaging) to other parts of the extension. + +Code for the background service worker is located at `src/app/background` directory - you need to use `defineBackground` within `index.ts` file inside to allow WXT to include your script in the build. + +```ts title="src/app/background/index.ts" +import { defineBackground } from "wxt/sandbox"; + +const main = () => { + console.log( + "Background service worker is running! Edit `src/app/background` and save to reload.", + ); +}; + +export default defineBackground(main); +``` + +To see the service worker in action, reload the extension, then open its "Service Worker inspector": + +![Service Worker inspector](/images/docs/extension/structure/sw-inspector.png) + +You should see what we've logged in the console: + +![Service Worker console](/images/docs/extension/structure/sw-log.png) + +To communicate with the service worker from other parts of the extension, you can use the [Messaging API](/docs/extension/structure/messaging). + +## Persisting state + + + Service workers in `dev` mode always remain in `active` state. + + +The worker becomes idle after a few seconds of inactivity, and the browser will kill its process entirely after 5 minutes. This means all state (variables, etc.) is lost unless you use a storage engine. + +The simplest way to persist your background service worker's state is to use the [storage API](/docs/extension/structure/storage). + +The more advanced way is to send the state to a remote database via our [backend API](/docs/extension/api/overview). + + + + + + + + + + diff --git a/.context/turbostarter-framework-context/sections/extension/structure/content-scripts.md b/.context/turbostarter-framework-context/sections/extension/structure/content-scripts.md new file mode 100644 index 0000000..056944e --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/structure/content-scripts.md @@ -0,0 +1,94 @@ +--- +title: Content scripts +description: Learn more about content scripts. +url: /docs/extension/structure/content-scripts +--- + +# Content scripts + +Content scripts run in the context of web pages in an isolated world. This allows multiple content scripts from various extensions to coexist without conflicting with each other's execution and to stay isolated from the page's JavaScript. + +A script that ends with `.ts` will not have front-end runtime (e.g. react) bundled with it and won't be treated as a ui script, while a script that ends in `.tsx` will be. + +There are many use cases for content scripts: + +* Injecting a custom stylesheet into the page +* Scraping data from the current web page +* Selecting, finding, and styling elements from the current web page +* Injecting UI elements into current web page + +Code for the content scripts is located in `src/app/content` directory - you need to define `.ts` or `.tsx` file inside and use `defineContentScript` to allow WXT to include your script in the build. + +```ts title="src/app/content/index.ts" +export default defineContentScript({ + matches: [""], + async main(ctx) { + console.log( + "Content script is running! Edit `app/content` and save to reload.", + ); + }, +}); +``` + +Reload your extension, open a web page, then open its inspector: + +![Content Script](/images/docs/extension/structure/content-script.png) +To learn more about content scripts, e.g. how to configure only specific pages to load content scripts, how to inject them into `window` object or how to fetch data inside, please check [the official documentation](https://wxt.dev/guide/essentials/content-scripts.html). + +## UI scripts + +WXT has first-class support for mounting React components into the current webpage. This feature is called content scripts UI (CSUI). + +![CSUI](/images/docs/extension/structure/csui.png) + +An extension can have as many CSUI as needed, with each CSUI targeting a group of webpages or a specific webpage. + +To get started with CSUI, create a `.tsx` file in `src/app/content` directory and use `defineContentScript` allow WXT to include your script in the build and mount your component into the current webpage: + +```tsx title="src/app/content/index.tsx" +const ContentScriptUI = () => { + return ( + + ); +}; + +export default defineContentScript({ + matches: [""], + cssInjectionMode: "ui", + async main(ctx) { + const ui = await createShadowRootUi(ctx, { + name: "turbostarter-extension", + position: "overlay", + anchor: "body", + onMount: (container) => { + const app = document.createElement("div"); + container.append(app); + + const root = ReactDOM.createRoot(app); + root.render(); + return root; + }, + onRemove: (root) => { + root?.unmount(); + }, + }); + + ui.mount(); + }, +}); +export default ContentScriptUI; +``` + + + The `.tsx` extension is essential to differentiate between Content Scripts UI and regular Content Scripts. Make sure to check if you're using appropriate type of content script for your use case. + + +To learn more about content scripts UI, e.g. how to inject custom styles, fonts or the whole lifecycle of a component, please check [the official documentation](https://wxt.dev/guide/essentials/content-scripts.html#ui). + + + Under the hood, the component is wrapped inside the component that implements the Shadow DOM technique, together with many helpful features. This isolation technique prevents the web page's style from affecting your component's styling and vice-versa. + + [Read more about the lifecycle of CSUI](https://docs.plasmo.com/framework/content-scripts-ui/life-cycle) + diff --git a/.context/turbostarter-framework-context/sections/extension/structure/messaging.md b/.context/turbostarter-framework-context/sections/extension/structure/messaging.md new file mode 100644 index 0000000..f858341 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/structure/messaging.md @@ -0,0 +1,95 @@ +--- +title: Messaging +description: Communicate between your extension's components. +url: /docs/extension/structure/messaging +--- + +# Messaging + +Messaging API makes communication between different parts of your extension easy. To make it simple and scalable, we're leveraging `@webext-core/messaging` library. + +It provides a declarative, type-safe, functional, promise-based API for sending, relaying, and receiving messages between your extension components. + +## Handling messages + +Based on our convention, we implemented a little abstraction on top of `@webext-core/messaging` to make it easier to use. That's why all types and keys are stored inside `lib/messaging` directory: + +```ts title="lib/messaging/index.ts" +import { defineExtensionMessaging } from "@webext-core/messaging"; + +export const Message = { + HELLO: "hello", +} as const; + +export type Message = (typeof Message)[keyof typeof Message]; + +interface Messages { + [Message.HELLO]: (message: string) => string; +} + +export const { onMessage, sendMessage } = defineExtensionMessaging(); +``` + +There you need to define what will be handled under each key. To make it more secure, only `Message` enum and `onMessage` and `sendMessage` functions are exported from the module. + +All message handlers are located in `src/app/background/messaging` directory under respective subdirectories. + +To create a message handler, create a TypeScript module in the `background/messaging` directory. Then, include your handlers for all keys related to the message: + +```ts title="app/background/messaging/hello.ts" +import { onMessage, Message } from "~/lib/messaging"; + +onMessage(Message.HELLO, (req) => { + const result = await querySomeApi(req.body.id); + + return result; +}); +``` + + + To make your handlers available across your extension, you need to import them + in the `background/index.ts` file. That way they could be interpreted by the + build process facilitated by WXT. + + +## Sending messages + +Extension pages, content scripts, or tab pages can send messages to the handlers using the `sendMessage` function. Since we orchestrate your handlers behind the scenes, the message names are typed and will enable autocompletion in your editor: + +```tsx title="app/popup/index.tsx" +import { sendMessage, Message } from "~/lib/messaging"; + +... + +const response = await sendMessage(Message.HELLO, "Hello, world!"); + +console.log(response); + +... +``` + +As it's an asynchronous operation, it's advisable to use [@tanstack/react-query](https://tanstack.com/query/latest/docs/framework/react/overview) integration to handle the response on the client side. + +We're already doing it that way when fetching auth session in the `User` component: + +```tsx title="hello.tsx" +export const Hello = () => { + const { data, isLoading } = useQuery({ + queryKey: [Message.HELLO], + queryFn: () => sendMessage(Message.HELLO, "Hello, world!"), + }); + + if (isLoading) { + return

Loading...

; + } + + /* do something with the data... */ + return

{data?.message}

; +}; +``` + + + + + + diff --git a/.context/turbostarter-framework-context/sections/extension/structure/overview.md b/.context/turbostarter-framework-context/sections/extension/structure/overview.md new file mode 100644 index 0000000..b540069 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/structure/overview.md @@ -0,0 +1,50 @@ +--- +title: Overview +description: Learn about the structure of the extension app. +url: /docs/extension/structure/overview +--- + +# Overview + +Every browser extension is different and can include different parts, removing the ones that are not needed. + +TurboStarter ships with all the things you need to start developing your own extension including: + +* **Popup window** - a small window that appears when the user clicks the extension icon. +* **Options page** - a page that appears when user enters extension settings. +* **Side panel** - a panel that appears when the user clicks sidepanel. +* **New tab page** - a page that appears when the user opens a new tab. +* **Devtools page** - a page that appears when the user opens the browser's devtools. +* **Tab pages** - custom pages shipped with the extension. +* **Content scripts** - injected scripts that run in the browser page. +* **Background scripts** - scripts that run in the background. +* **Message passing** - a way to communicate between different parts of the extension. +* **Storage** - a way to store data in the extension. + +All the entrypoints are defined in `apps/extension/src/app` directory (it's similar to file-based routing in Next.js and Expo). + +This directory acts as a source for WXT framework which is used to build the extension. It has the following structure: + + + + + + + + + + + + + + + + + + + + + +By structurizing it this way, we can easily add new entrypoints in the future and extend rest of the extension independently from each other. + +We'll go through each part and explain the purpose of it, check following sections for more details: diff --git a/.context/turbostarter-framework-context/sections/extension/structure/pages.md b/.context/turbostarter-framework-context/sections/extension/structure/pages.md new file mode 100644 index 0000000..5157e6d --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/structure/pages.md @@ -0,0 +1,77 @@ +--- +title: Pages +description: Get started with your extension's pages. +url: /docs/extension/structure/pages +--- + +# Pages + +Extension pages are built-in pages recognized by the browser. They include the extension's popup, options, sidepanel and newtab pages. + + + As WXT is based on Vite, it has very powerful [HMR support](https://vite.dev/guide/features#hot-module-replacement). This means that you don't need to refresh the extension manually when you make changes to the code. + + +## Popup + +The popup page is a small dialog window that opens when a user clicks on the extension's icon in the browser toolbar. It is the most common type of extension page. + +![Popup window](/images/docs/extension/structure/popup.png) + + + + + + + +## Options + +The options page is meant to be a dedicated place for the extension's settings and configuration. + +![Options page](/images/docs/extension/structure/options.png) + + + +## Devtools + +The devtools page is a custom page (including panels) that opens when a user opens the extension's devtools panel. + +![Devtools page](/images/docs/extension/structure/devtools.png) + + + +## New tab + +The new tab page is a custom page that opens when a user opens a new tab in the browser. + +![New tab page](/images/docs/extension/structure/newtab.png) + + + +## Side panel + +The side panel is a custom page that opens when a user clicks on the extension's icon in the browser toolbar. + +![Side panel](/images/docs/extension/structure/sidepanel.png) + + + +## Tabs + +Unlike traditional extension pages, tab (unlisted) pages are just regular web pages shipped with your extension bundle. Extensions generally redirect to or open these pages programmatically, but you can link to them as well. + +They could be useful for following cases: + +* when you want to show a some page when user first installs your extension +* when you want to have dedicated pages for authentication +* when you need more advanced routing setup + +![Tab page](/images/docs/extension/structure/tabs.png) + +Your tab page will be available under the `/tabs` path in the extension bundle. It will be accessible from the browser under the URL: + +``` +chrome-extension:///tabs/your-tab-page.html +``` + + diff --git a/.context/turbostarter-framework-context/sections/extension/structure/storage.md b/.context/turbostarter-framework-context/sections/extension/structure/storage.md new file mode 100644 index 0000000..64c0758 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/structure/storage.md @@ -0,0 +1,109 @@ +--- +title: Storage +description: Learn how to store data in your extension. +url: /docs/extension/structure/storage +--- + +# Storage + +TurboStarter leverages `wxt/storage` library to handle persistent storage for your extension. It's a utility library from that abstracts the persistent storage API available to browser extensions. + +It falls back to localStorage when the extension storage API is unavailable, allowing for state sync between extension pages, content scripts, background service workers and web pages. + + + To use the `wxt/storage` API, the "storage" permission **must** be added to the manifest: + + ```ts title="wxt.config.ts" + export default defineConfig({ + manifest: { + permissions: ["storage"], + }, + }); + ``` + + +## Storing data + +The base Storage API is designed to be easy to use. It is usable in every extension runtime such as background service workers, content scripts and extension pages. + +TurboStarter ships with predefined storage used to handle [theming](/docs/extension/customization/styling) in your extension, but you can create your own storage as well. + +All storage-related methods and types are located in `lib/storage` directory. + +```ts title="lib/storage/index.ts" +export const StorageKey = { + THEME: "local:theme", +} as const; + +export type StorageKey = (typeof StorageKey)[keyof typeof StorageKey]; +``` + +Then, to make it available around your extension, we're setting it up and providing default values: + +```ts title="lib/storage/index.ts" +import { storage as browserStorage } from "wxt/storage"; + +import { appConfig } from "~/config/app"; + +import type { ThemeConfig } from "@turbostarter/ui"; + +const storage = { + [StorageKey.THEME]: browserStorage.defineItem(StorageKey.THEME, { + fallback: appConfig.theme, + }), +} as const; +``` + +To learn more about customizing your storage, syncing state or setup automatic backups please refer to the [official documentation](https://wxt.dev/storage.html). + +## Consuming storage + +To consume storage in your extension, you can use the `useStorage` React hook that is automatically provided to every part of the extension. The hook API is designed to streamline the state-syncing workflow between the different pieces of an extension. + +Here is an example on how to consume our theme storage in `Layout` component: + +```tsx title="modules/common/layout/layout.tsx" +import { StorageKey, useStorage } from "~/lib/storage"; + +export const Layout = ({ children }: { children: React.ReactNode }) => { + const { data } = useStorage(StorageKey.THEME); + + return ( +
+ {children} +
+ ); +}; +``` + +Congrats! You've just learned how to persist and consume global data in your extension 🎉 + +For more advanced use cases, please refer to the [official documentation](https://wxt.dev/storage.html). + +### Usage with Firefox + +To use the storage API on Firefox during development you need to add an addon ID to your manifest, otherwise, you will get this error: + +> Error: The storage API will not work with a temporary addon ID. Please add an explicit addon ID to your manifest. For more information see [https://mzl.la/3lPk1aE](https://mzl.la/3lPk1aE) + +To add an addon ID to your manifest, add this to your package.json: + +```ts title="wxt.config.ts" +export default defineConfig({ + manifest: { + browser_specific_settings: { + gecko: { + id: "your-id@example.com", + }, + }, + }, +}); +``` + +During development, you may use any ID. If you have published your extension, you need to use the ID assigned by [Firefox Add-ons](https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons). + + + + + + diff --git a/.context/turbostarter-framework-context/sections/extension/tests/e2e.md b/.context/turbostarter-framework-context/sections/extension/tests/e2e.md new file mode 100644 index 0000000..b6efd83 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/tests/e2e.md @@ -0,0 +1,15 @@ +--- +title: E2E tests +description: Simulate real user scenarios across the entire stack with automated end-to-end test tools and examples. +url: /docs/extension/tests/e2e +--- + +# E2E tests + + + End-to-end (E2E) tests will be available soon, allowing you to automate testing of real user flows and interactions across your application. + + Stay tuned for updates as we roll out robust E2E testing resources and examples. + + [See roadmap](https://github.com/orgs/turbostarter/projects/1) + diff --git a/.context/turbostarter-framework-context/sections/extension/tests/unit.md b/.context/turbostarter-framework-context/sections/extension/tests/unit.md new file mode 100644 index 0000000..26fbd34 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/tests/unit.md @@ -0,0 +1,136 @@ +--- +title: Unit tests +description: Write and run fast unit tests for individual functions and components with instant feedback. +url: /docs/extension/tests/unit +--- + +# Unit tests + +Unit tests are a type of automated test where individual units or components are tested. The "unit" in "unit test" refers to the smallest testable parts of an application. These tests are designed to verify that each unit of code performs as expected. + +TurboStarter uses [Vitest](https://vitest.dev) as the unit testing framework. It's a blazing-fast test runner built on top of [Vite](https://vitejs.dev), designed for modern JavaScript and TypeScript projects. + + + If you've used [Jest](https://jestjs.io) before, you already know Vitest - it shares the same API. But Vitest is built for speed: native TypeScript support without transpilation, parallel test execution, and a smart watch mode that only re-runs tests affected by your changes. + + It comes with everything you need out of the box - code coverage, snapshot testing, mocking, and a slick UI for debugging. Fast feedback, zero configuration. + + +## Why write unit tests? + +Unit tests give you **fast, focused feedback** on small pieces of your code - individual functions, hooks, or components. Instead of debugging an entire page or flow, you can verify just the logic you care about in isolation. + +They also act as **living documentation**: a good test tells you how a function is supposed to behave, which edge cases are important, and what assumptions the code makes. This makes it much easier to safely refactor or extend features later. + +In TurboStarter, unit tests are designed to be **cheap and quick to run**, so you can keep Vitest running in watch mode while you code. Every change you make is immediately checked, helping you catch regressions before they ever reach integration or end‑to‑end tests. + +## Configuration + +TurboStarter configures Vitest to be **as simple as possible**, while still taking advantage of [Turborepo's caching](https://turborepo.com/docs/crafting-your-repository/caching) and Vitest's [Test Projects](https://vitest.dev/guide/projects). + +```ts title="vitest.config.ts" +import { mergeConfig } from "vitest/config"; + +import baseConfig from "@turbostarter/vitest-config/base"; + +export default mergeConfig(baseConfig, { + test: { + /* your extended test configuration here */ + }, +}); +``` + +* **Per-package tests**: each package that has unit tests defines its own `test` script. This keeps the configuration close to the code and makes it easy to add tests to any workspace. +* **Turbo tasks for CI**: the root `test` task (`pnpm test`) uses `turbo run test` to execute all package-level test scripts with smart caching, which is ideal for CI pipelines where you want to avoid re-running unchanged tests. +* **Vitest Test Projects for local dev**: a root Vitest configuration uses [Test Projects](https://vitest.dev/guide/projects) to run all unit test suites from a single command, which is perfect for local development when you want fast feedback across the whole monorepo. + +This **hybrid setup** combines Turborepo and Vitest Projects in a way that fits TurboStarter's principles: cached, package-aware runs in CI, and a single, unified Vitest entry point for local development. + +You can read more about this setup in the official documentation guides listed below. + + + + + + + +## Running tests + +There are a few different ways to run unit tests, depending on what you're doing: + +* **CI / full test run** - at the root of the repo: + +```bash +pnpm test +``` + +This runs `turbo run test`, which executes all `test` scripts in packages that define them, with Turborepo handling caching so unchanged packages are skipped. This is what you should use in your CI/CD pipeline. + +* **One-off local run with Vitest Projects**: + +```bash +pnpm test:projects +``` + +This uses Vitest [Test Projects](https://vitest.dev/guide/projects) to run all configured unit test suites from a single command, which is great when you want to quickly validate the whole monorepo locally. + +* **Watch mode during development**: + +```bash +pnpm test:projects:watch +``` + +This starts Vitest in watch mode across all Test Projects. As you edit files, only the affected tests are re-run, giving you fast feedback while you work. + +## Code coverage + +Unit test coverage helps you understand **how much** of your code is being tested. While it can't guarantee bug-free code, it shines a light on untested paths that could hide issues or regressions. + +To generate a code coverage report for all unit tests, run: + +```bash +pnpm turbo test:coverage +``` + +This command runs the coverage task across all relevant packages (using Turborepo) and collects the results into a single coverage output. + +To open the coverage report in your browser: + +```bash +pnpm turbo test:coverage:view +``` + +This will build the HTML report and launch it using your default browser, so you can explore which files and branches are covered. + + + You can also store the generated coverage report as a [GitHub Actions artifact](https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts) during your CI/CD pipeline, just add the following steps to your workflow job: + + ```yaml title=".github/workflows/ci.yml" + # your workflow job configuration here + + - name: 📊 Generate coverage + run: pnpm turbo test:coverage + + - name: 🗃️ Archive coverage report + uses: actions/upload-artifact@v5 + with: + name: coverage-${{ github.sha }} + path: tooling/vitest/coverage/report + ``` + + This will generate a test coverage report and upload it as an artifact, so you can access it from GitHub Actions tab for later inspection. + + +A high coverage percentage means your tests execute most lines and branches - but the quality and relevance of your tests matter more than the raw number. Use coverage reports to spot gaps and guide improvements, not as the sole metric of test health. + +![Code coverage](/images/docs/code-coverage.png) + +## Best practices + +Unit tests should work **for you**, not the other way around. Focus on writing tests that make it easier to change code with confidence, not on satisfying arbitrary rules or reaching a magic number in a dashboard. + +Code coverage is a **useful metric**, but it **SHOULD NOT** be the goal. It's better to have a smaller set of high‑value tests that cover critical paths and edge cases than a huge suite of fragile tests that are hard to maintain. + +When in doubt, ask: *“Does this test give **me** confidence that I can change this code without breaking users?”* If the answer is no, refactor or remove it. + +Finally, keep unit tests focused on **small, isolated pieces of logic**. More advanced flows — like multi-step user journeys, cross-service interactions, or full-page behavior — are better covered by [end-to-end (E2E) tests](/docs/web/tests/e2e), where you can verify the system as a whole. diff --git a/.context/turbostarter-framework-context/sections/extension/troubleshooting/installation.md b/.context/turbostarter-framework-context/sections/extension/troubleshooting/installation.md new file mode 100644 index 0000000..edbfb49 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/troubleshooting/installation.md @@ -0,0 +1,79 @@ +--- +title: Installation +description: Find answers to common extension installation issues. +url: /docs/extension/troubleshooting/installation +--- + +# Installation + +## Cannot clone the repository + +Issues related to cloning the repository are usually related to a Git misconfiguration in your local machine. The commands displayed in this guide using SSH: these will work only if you have setup your SSH keys in Github. + +If you run into issues, [please make sure you follow this guide to set up your SSH key in Github.](https://docs.github.com/en/authentication/connecting-to-github-with-ssh) + +If this also fails, please use HTTPS instead. You will be able to see the commands in the repository's Github page under the "Clone" dropdown. + +Please also make sure that the account that accepted the invite to TurboStarter, and the locally connected account are the same. + +## Local database doesn't start + +If you cannot run the local database container, it's likely you have not started [Docker](https://docs.docker.com/get-docker/) locally. Our local database requires Docker to be installed and running. + +Please make sure you have installed Docker (or compatible software such as [Colima](https://github.com/abiosoft/colima), [Orbstack](https://github.com/orbstack/orbstack)) and that is running on your local machine. + +Also, make sure that you have enough [memory and CPU allocated](https://docs.docker.com/engine/containers/resource_constraints/) to your Docker instance. + +## Permissions issues + +If some feature of your extension is not working, it's possible that you're missing a permission in the manifest config. + +Make sure to check the [permissions](/docs/extension/configuration/manifest#overriding-manifest) section in the manifest config file. + +## I don't see my translations + +If you don't see your translations appearing in the application, there are a few common causes: + +1. Check that your translation `.json` files are properly formatted and located in the correct directory +2. Verify that the language codes in your configuration match your translation files +3. Enable debug mode (`debug: true`) in your i18next configuration to see detailed logs + +[Read more about configuration for translations](/docs/extension/internationalization#configuration) + +## "Module not found" error + +This issue is mostly related to either dependency installed in the wrong package or issues with the file system. + +The most common cause is incorrect dependency installation. Here's how to fix it: + +1. Clean the workspace: + + ```bash + pnpm clean + ``` + +2. Reinstall the dependencies: + ```bash + pnpm i + ``` + +If you're adding new dependencies, make sure to install them in the correct package: + +```bash +# For main app dependencies +pnpm install --filter mobile my-package + +# For a specific package +pnpm install --filter @turbostarter/ui my-package +``` + +If the issue persists, please check the file system for any issues. + +### Windows OneDrive + +OneDrive can cause file system issues with Node.js projects due to its file syncing behavior. If you're using Windows with OneDrive, you have two options to resolve this: + +1. Move your project to a location outside of OneDrive-synced folders (recommended) +2. Disable OneDrive sync specifically for your development folder + +This prevents file watching and symlink issues that can occur when OneDrive tries to sync Node.js project files. diff --git a/.context/turbostarter-framework-context/sections/extension/troubleshooting/publishing.md b/.context/turbostarter-framework-context/sections/extension/troubleshooting/publishing.md new file mode 100644 index 0000000..abba4a9 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/extension/troubleshooting/publishing.md @@ -0,0 +1,62 @@ +--- +title: Publishing +description: Find answers to common publishing issues. +url: /docs/extension/troubleshooting/publishing +--- + +# Publishing + +## My extension submission was rejected + +If your extension submission was rejected, you probably got an email with the reason. You'll need to fix the issues and upload a new build of your extension to the store and send it for review again. + +Make sure to follow the [guidelines](/docs/extension/marketing) when submitting your extension to ensure that everything is setup correctly. + +## Version number mismatch + +If you get version number conflicts when submitting: + +1. Ensure your `manifest.json` version matches what's in the store +2. Increment the version number appropriately for each new submission +3. Make sure the version follows semantic versioning (e.g., `1.0.1`) + +## Missing permissions in manifest + +If your extension is rejected due to permission issues: + +1. Review the permissions declared in your `manifest.json` +2. Ensure all permissions are properly justified in your submission +3. Remove any unused permissions that aren't essential +4. Consider using optional permissions where possible + +[Learn more about permissions](/docs/extension/configuration/manifest#permissions) + +## Content Security Policy (CSP) violations + +If your extension is rejected due to CSP issues: + +1. Check your manifest's `content_security_policy` field +2. Ensure all external resources are properly whitelisted +3. Remove any unsafe inline scripts or eval usage +4. Use more secure alternatives like `browser.scripting.executeScript` + +## My extension crashes on production build + +If the extension works during development but crashes after publishing or when loaded unpacked in production mode, check these common causes: + +1. **Uncaught runtime errors** in the background service worker or content scripts. Open `chrome://extensions` (or `about:debugging` in Firefox) → enable Developer mode → Inspect the service worker/content script and check the console for stack traces. +2. **Missing permissions or host permissions** causing APIs to throw (e.g., network calls, tabs access). Ensure required `permissions` and `host_permissions` are declared in `manifest.json`. +3. **CSP blocking resources** (inline scripts/styles, remote fonts, or endpoints). Verify `content_security_policy` and update code to avoid unsafe patterns. +4. **Missing assets or incorrect paths** referenced in `manifest.json` (`icons`, `web_accessible_resources`, `action.default_popup`, etc.). Confirm files exist in the final build output and paths match. +5. **Build-time variables not resolved**. If you rely on environment variables, ensure they’re inlined at build time or have safe fallbacks at runtime. Example: + ```js + const apiUrl = env.VITE_SITE_URL ?? "https://api.example.com"; + ``` +6. **Module format or bundler config issues** (MV3 service worker must be ESM if `type: 'module'`). Align bundler output with your manifest expectations and rebuild. + +Try this: + +1. Reproduce with a production bundle locally and load it as an unpacked extension; inspect background and content script logs for errors. +2. Validate `manifest.json` and ensure all referenced files are present in the build output. +3. Temporarily relax CSP locally to confirm whether CSP is the cause; then apply a compliant fix (don’t ship relaxed CSP). +4. Add fallbacks for any build-time variables and rebuild. diff --git a/.context/turbostarter-framework-context/sections/mobile/ai.md b/.context/turbostarter-framework-context/sections/mobile/ai.md new file mode 100644 index 0000000..6740868 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/ai.md @@ -0,0 +1,59 @@ +--- +title: AI +description: Learn how to use AI integration in your mobile app. +url: /docs/mobile/ai +--- + +# AI + +As AI integration for [web](/docs/web/ai/overview), [extension](/docs/extension/ai), and mobile is based on the same battle-tested [Vercel AI SDK](https://sdk.vercel.ai/docs/introduction), the implementation is very similar across platforms. + +In this section, we'll focus on how to consume AI responses in the mobile app. For server-side implementation details, please refer to the [web documentation](/docs/web/ai/overview). + +## Features + +The most common AI integration features are also supported in the mobile app: + +* **Chat**: Build chat interfaces inside native mobile apps. +* **Streaming**: Receive AI responses as soon as the model starts generating them, without waiting for the full response to be completed. +* **Image generation**: Generate images based on a given prompt. + +You can easily compose your application using these building blocks or extend them to suit your specific needs. + +## Usage + +The usage of AI integration in the mobile app is the same as for [web app](/docs/web/ai/configuration#client-side) and [browser extension](/docs/extension/ai#server--client). We use the exact same [API endpoint](/docs/web/ai/configuration#api-endpoint), and since TurboStarter ships with built-in support for streaming on mobile, we can leverage it to display answers incrementally to the user as they're generated. + +```tsx title="ai.tsx" +import { useChat } from "@ai-sdk/react"; +import { DefaultChatTransport } from "ai"; + +const AI = () => { + const { messages } = useChat({ + transport: new DefaultChatTransport({ + api: "/api/ai/chat", + }), + }); + + return ( + + {messages.map((message) => ( + + {message.parts.map((part, i) => { + switch (part.type) { + case "text": + return {part.text}; + } + })} + + ))} + + ); +}; + +export default AI; +``` + +By leveraging this integration, we can easily manage the state of the AI request and update the UI as soon as the response is ready. + +TurboStarter ships with a ready-to-use implementation of AI chat, allowing you to see this solution in action. Feel free to reuse or modify it according to your needs. diff --git a/.context/turbostarter-framework-context/sections/mobile/analytics/configuration.md b/.context/turbostarter-framework-context/sections/mobile/analytics/configuration.md new file mode 100644 index 0000000..2f32c1a --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/analytics/configuration.md @@ -0,0 +1,161 @@ +--- +title: Configuration +description: Learn how to configure mobile analytics in TurboStarter. +url: /docs/mobile/analytics/configuration +--- + +# Configuration + +The `@turbostarter/analytics-mobile` package offers a streamlined and flexible approach to tracking events in your TurboStarter mobile app using various analytics providers. It abstracts the complexities of different analytics services and provides a consistent interface for event tracking. + +In this section, we'll guide you through the configuration process for each supported provider. + +Note that the configuration is validated against a schema, so you'll see error messages in the console if anything is misconfigured. + +## Permissions + +First and foremost, to start tracking any metrics from your app (and to do so legally), you need to ask your users for permission. It's [required](https://support.apple.com/en-us/102420), and you're not allowed to collect any data without it. + +To make this process as simple as possible, TurboStarter comes with a `useTrackingPermissions` hook that you can use to access the user's consent status. It will handle asking for permission automatically as well as process updates made through the general phone settings. + +```tsx +import { useTrackingPermissions } from "@turbostarter/analytics-mobile"; + +export const MyComponent = () => { + const granted = useTrackingPermissions(); + + if (granted) { + // Start tracking + } else { + // Disable tracking + } +}; +``` + +Also, for Apple, you must declare the tracking justification via [App Tracking Transparency](https://developer.apple.com/documentation/apptrackingtransparency). It comes pre-configured in TurboStarter via the [Expo Config Plugin](https://docs.expo.dev/versions/latest/config/app/#plugins), where you can provide a custom message to the user: + +```ts title="app.config.ts" +export default ({ config }: ConfigContext): ExpoConfig => ({ + ...config, + plugins: [ + [ + "expo-tracking-transparency", + { + /* 🍎 Describe why you need access to the user's data */ + userTrackingPermission: + "This identifier will be used to deliver personalized ads to you.", + }, + ], + ], +}); +``` + +This way, we ensure that the user is aware of the data we collect and can make an informed decision. If you don't provide this information, your app is likely to be rejected by Apple and/or Google during the [review process](/docs/mobile/publishing/checklist#send-to-review). + +## Providers + +TurboStarter supports multiple analytics providers, each with its own unique configuration. Below, you'll find detailed information on how to set up and use each supported provider. Choose the one that best suits your needs and follow the instructions in the respective accordion section. + + + + To use Google Analytics as your analytics provider, you need to [configure and link a Firebase project to your app](/docs/mobile/installation/firebase). + + After that, you can proceed with the installation of the analytics package: + + ```bash + pnpm add --filter @turbostarter/analytics-mobile @react-native-firebase/analytics + ``` + + Also, make sure to activate the Google Analytics provider as your analytics provider by updating the exports in: + + ```ts title="index.ts" + // [!code word:google-analytics] + export * from "./google-analytics"; + export * from "./google-analytics/env"; + ``` + + To customize the provider, you can find its definition in `packages/analytics/mobile/src/providers/google-analytics` directory. + + For more information, please refer to the [React Native Firebase documentation](https://rnfirebase.io/analytics/usage). + + ![Google Analytics dashboard](/images/docs/web/analytics/google/dashboard.jpg) + + + + + PostHog is also one of pre-configured providers for [monitoring](/docs/mobile/monitoring/overview) in TurboStarter mobile apps. You can learn more about it [here](/docs/mobile/monitoring/posthog). + + + To use PostHog as your analytics provider, you need to configure a PostHog instance. You can obtain the [Cloud](https://app.posthog.com/signup) instance by [creating an account](https://app.posthog.com/signup) or [self-host](https://posthog.com/docs/self-host) it. + + Then, create a project and, based on your [project settings](https://app.posthog.com/project/settings), fill the following environment variables in your `.env.local` file in `apps/mobile` directory and your `eas.json` file: + + ```dotenv + EXPO_PUBLIC_POSTHOG_KEY="your-posthog-api-key" + EXPO_PUBLIC_POSTHOG_HOST="your-posthog-instance-host" + ``` + + Also, make sure to activate the PostHog provider as your analytics provider by updating the exports in: + + ```ts title="index.ts" + // [!code word:posthog] + export * from "./posthog"; + export * from "./posthog/env"; + ``` + + To customize the provider, you can find its definition in `packages/analytics/mobile/src/providers/posthog` directory. + + For more information, please refer to the [PostHog documentation](https://posthog.com/docs). + + ![PostHog dashboard](/images/docs/web/analytics/posthog.png) + + + + To use Mixpanel as your analytics provider, you need to [create an account](https://mixpanel.com/) and [obtain your project token](https://help.mixpanel.com/hc/en-us/articles/115004502806-Find-Project-Token). + + Then, set it as an environment variable in your `.env.local` file in the `apps/mobile` directory and your `eas.json` file: + + ```dotenv + EXPO_PUBLIC_MIXPANEL_TOKEN="your-project-token" + ``` + + Also, make sure to activate the Mixpanel provider as your analytics provider by updating the exports in: + + ```ts title="index.ts" + // [!code word:mixpanel] + export * from "./mixpanel"; + export * from "./mixpanel/env"; + ``` + + To customize the provider, you can find its definition in `packages/analytics/mobile/src/providers/mixpanel` directory. + + For more information, please refer to the [Mixpanel documentation](https://docs.mixpanel.com/). + + + +## Context + +To enable tracking events, capturing screen views and other analytics features, you need to wrap your app with the `Provider` component that's implemented by every provider and available through the `@turbostarter/analytics-mobile` package: + +```tsx title="providers.tsx" +// [!code word:AnalyticsProvider] +import { memo } from "react"; + +import { Provider as AnalyticsProvider } from "@turbostarter/analytics-mobile"; + +interface ProvidersProps { + readonly children: React.ReactNode; +} + +export const Providers = memo(({ children }) => { + return ( + + {children} + + ); +}); + +Providers.displayName = "Providers"; +``` + +By implementing this setup, you ensure that all analytics events are properly tracked from your mobile app code. This configuration allows you to safely utilize the [Analytics API](/docs/mobile/analytics/tracking) within your components, enabling comprehensive event tracking and data collection. diff --git a/.context/turbostarter-framework-context/sections/mobile/analytics/overview.md b/.context/turbostarter-framework-context/sections/mobile/analytics/overview.md new file mode 100644 index 0000000..998cede --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/analytics/overview.md @@ -0,0 +1,50 @@ +--- +title: Overview +description: Get started with mobile analytics in TurboStarter. +url: /docs/mobile/analytics/overview +--- + +# Overview + +When it comes to mobile app analytics, we can distinguish between two types: + +* **Store listing analytics**: Used to track the performance of your mobile app's store listing (e.g., how many people have viewed your app in the store or how many have installed it). +* **In-app analytics**: Tracks user actions within your mobile app (e.g., how many users entered a specific screen, how many users clicked on a specific button, etc.). + +The `@turbostarter/analytics-mobile` package provides a set of tools to easily implement both types of analytics in your mobile app. + +## Store listing analytics + +Interpreting your mobile app's store listing metrics can help you evaluate how changes to your app and store listing affect conversion rates. For example, you can identify keywords that users are searching for to optimize your app's store listing. + +While each store implements a different set of metrics, there are some common ones you should be aware of: + +* **Downloads**: The total number of times your app was downloaded, including both first-time downloads and re-downloads. +* **Sales**: The total number of pre-orders, first-time app downloads, in-app purchases, and their associated sales. +* **Usage**: A variety of user engagement metrics, such as installations, sessions, crashes, and active devices. + +To learn more about these or other metrics (e.g., how to create custom reports or KPIs), please refer to the official documentation of the store you're publishing to: + + + + + + + +## In-app analytics + +TurboStarter comes with built-in analytics support for multiple providers as well as a unified API for tracking events. This API enables you to easily and consistently track user behavior and app usage across your mobile application. + +To learn more about each provider and how to configure them, see their respective sections: + + + + + + + + + +All configuration and setup is built-in with a unified API, allowing you to switch between providers by simply changing the exports. You can even introduce your own provider without breaking any tracking-related logic. + +In the following sections, we'll cover how to set up each provider and how to track events in your application. diff --git a/.context/turbostarter-framework-context/sections/mobile/analytics/tracking.md b/.context/turbostarter-framework-context/sections/mobile/analytics/tracking.md new file mode 100644 index 0000000..79d6f58 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/analytics/tracking.md @@ -0,0 +1,84 @@ +--- +title: Tracking events +description: Learn how to track events in your TurboStarter mobile app. +url: /docs/mobile/analytics/tracking +--- + +# Tracking events + +The strategy for tracking events that every provider has to implement is extremely simple: + +```ts +export type AllowedPropertyValues = string | number | boolean; + +type TrackFunction = ( + event: string, + data?: Record, +) => void; + +export interface AnalyticsProviderStrategy { + Provider: ({ children }: { children: React.ReactNode }) => React.ReactNode; + track: TrackFunction; +} +``` + + + You don't need to worry much about this implementation, as all the providers are already configured for you. However, it's useful to be aware of this structure if you plan to add your own custom provider. + + +As shown above, each provider must supply two key elements: + +1. `Provider` - a component that [wraps your app](/docs/mobile/analytics/configuration#context). +2. `track` - a function responsible for sending event data to the provider. + +To track an event, you simply need to invoke the `track` method, passing the event name and an optional data object: + +```tsx +import { track } from "@turbostarter/analytics-mobile"; + +export const MyComponent = () => { + return ( + track("button.click", { country: "US" })}> + Track event + + ); +}; +``` + +In most mobile apps, you'll only ever need to use the `track` method to track events. You can use it anywhere in your app code—such as in response to user interactions, navigation events, or custom actions - by simply calling `track` with an event name and optional properties. + +## Identifying users + +Linking events to specific users enables you to build a full picture of how they're using your product across different sessions, devices, and platforms. + +For identification purposes, we're extending the strategy with the `identify` and `reset` methods. They are optional and only needed if you want to identify users in your app and associate their actions with a specific user ID. + +```ts +type IdentifyFunction = ( + userId: string, + traits?: Record, +) => void; + +export interface AnalyticsProviderClientStrategy { + identify: IdentifyFunction; + reset: () => void; +} +``` + +To identify users, call the `identify` method, passing the user's ID and an optional traits object: + +```tsx +import { identify } from "@turbostarter/analytics-mobile"; + +identify("user-123", { name: "John Doe" }); +``` + +This will associate all future events with the user's ID, allowing you to track user behavior and gain valuable insights into your application's usage patterns. + + + The `identify` method is configured out-of-the-box to react on changes to the user's authentication state. + + When the user is authenticated, the `identify` method will be called with the user's ID and the user's traits. When the user is logged out, the `reset` method will be called to clear the existing user identification. + + +Congratulations! You've now mastered event tracking in your TurboStarter mobile app. With this knowledge, you're well-equipped to analyze user behaviors and gain valuable insights into your application's usage patterns. Happy analyzing! diff --git a/.context/turbostarter-framework-context/sections/mobile/api/client.md b/.context/turbostarter-framework-context/sections/mobile/api/client.md new file mode 100644 index 0000000..7596843 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/api/client.md @@ -0,0 +1,213 @@ +--- +title: Using API client +description: How to use API client to interact with the API. +url: /docs/mobile/api/client +--- + +# Using API client + +In mobile app code, you can only access the API client from the **client-side.** + +When you create a new component or screen and want to fetch some data, you can use the API client to do so. + +## Creating a client + +We're creating a client-side API client in `apps/mobile/src/lib/api/index.tsx` file. It's a simple wrapper around the [@tanstack/react-query](https://tanstack.com/query/latest/docs/framework/react/overview) that fetches or mutates data from the API. + +It also requires wrapping your app in a `QueryClientProvider` component to provide the API client to the rest of the app: + +```tsx title="_layout.tsx" +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + + ... + + ... + + + + + ); +} +``` + + + Inside the `apps/mobile/src/lib/api/utils.ts` file we're calling a function to get base url of your api, so make sure it's set correctly (especially on production) and your web api endpoint is corresponding with the name there. + + ```tsx title="utils.ts" + const getBaseUrl = () => { + /** + * Gets the IP address of your host-machine. If it cannot automatically find it, + * you'll have to manually set it. NOTE: Port 3000 should work for most but confirm + * you don't have anything else running on it, or you'd have to change it. + * + * **NOTE**: This is only for development. In production, you'll want to set the + * baseUrl to your production API URL. + */ + const debuggerHost = Constants.expoConfig?.hostUri; + const localhost = debuggerHost?.split(":")[0]; + + if (!localhost) { + console.warn("Failed to get localhost. Pointing to production server..."); + return env.EXPO_PUBLIC_SITE_URL; + } + return `http://${localhost}:3000`; + }; + ``` + + As you can see we're relying on your machine IP address for local development (in case you want to open the app from another device) or on the [environment variables](/docs/mobile/configuration/environment-variables) in production to get it, so there shouldn't be any issues with it, but in case, please be aware where to find it 😉 + + +## Queries + +Of course, everything comes already configured for you, so you just need to start using `api` in your components/screens. + +For example, to fetch the list of posts you can use the `useQuery` hook: + +```tsx title="app/(tabs)/tab-one.tsx" +import { api } from "~/lib/api"; + +export default function TabOneScreen() { + const { data: posts, isLoading } = useQuery({ + queryKey: ["posts"], + queryFn: async () => { + const response = await api.posts.$get(); + + if (!response.ok) { + throw new Error("Failed to fetch posts!"); + } + + return response.json(); + }, + }); + + if (isLoading) { + return Loading...; + } + + /* do something with the data... */ + return ( + + {JSON.stringify(posts)} + + ); +} +``` + +It's using the `@tanstack/react-query` [useQuery API](https://tanstack.com/query/latest/docs/framework/react/reference/useQuery), so you shouldn't have any troubles with it. + + + + + + + +## Mutations + +If you want to perform a mutation in your mobile code, you can use the `useMutation` hook that comes straight from the integration with [Tanstack Query](https://tanstack.com/query): + +```tsx title="form.tsx" +import { api } from "~/lib/api"; + +export function CreatePost() { + const queryClient = useQueryClient(); + const { mutate } = useMutation({ + mutationFn: async (post: PostInput) => { + const response = await api.posts.$post(post); + + if (!response.ok) { + throw new Error("Failed to create post!"); + }, + }, + onSuccess: () => { + toast.success("Post created successfully!"); + queryClient.invalidateQueries({ queryKey: ["posts"] }); + }, + }); + + return ( + + + + ); +} +``` + +Here, we're also invalidating the query after the mutation is successful. This is a very important step to make sure that the data is updated in the UI. + + + + + + + +## Handling responses + +As you can see in the examples above, the [Hono RPC](https://hono.dev/docs/guides/rpc) client returns a plain `Response` object, which you can use to get the data or handle errors. However, implementing this handling in every query or mutation can be tedious and will introduce unnecessary boilerplate in your codebase. + +That's why we've developed the `handle` function that unwraps the response for you, handles errors, and returns the data in a consistent format. You can safely use it with any procedure from the API client: + + + + ```tsx + // [!code word:handle] + import { handle } from "@turbostarter/api/utils"; + + import { api } from "~/lib/api"; + + export default function TabOneScreen() { + const { data: posts, isLoading } = useQuery({ + queryKey: ["posts"], + queryFn: handle(api.posts.$get), + }); + + if (isLoading) { + return Loading...; + } + + /* do something with the data... */ + return ( + + {JSON.stringify(posts)} + + ); + } + ``` + + + + ```tsx + // [!code word:handle] + import { handle } from "@turbostarter/api/utils"; + + import { api } from "~/lib/api/client"; + + export default function CreatePost() { + const queryClient = useQueryClient(); + const { mutate } = useMutation({ + mutationFn: handle(api.posts.$post), + onSuccess: () => { + toast.success("Post created successfully!"); + queryClient.invalidateQueries({ queryKey: ["posts"] }); + }, + }); + + return ( +
+ +
+ ); + } + ``` +
+
+ +With this approach, you can focus on the business logic instead of repeatedly writing code to handle API responses in your browser extension components, making your extension's codebase more readable and maintainable. + +The same error handling and response unwrapping benefits apply whether you're building web, mobile, or extension interfaces - allowing you to keep your data fetching logic consistent across all platforms. diff --git a/.context/turbostarter-framework-context/sections/mobile/api/overview.md b/.context/turbostarter-framework-context/sections/mobile/api/overview.md new file mode 100644 index 0000000..26688f1 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/api/overview.md @@ -0,0 +1,55 @@ +--- +title: Overview +description: Get started with the API. +url: /docs/mobile/api/overview +--- + +# Overview + + + To enable communication between your Expo app and the server in a production environment, the web application with Hono API must be deployed first. + + + + + + + + +TurboStarter is designed to be a scalable and production-ready full-stack starter kit. One of its core features is a dedicated and extensible API layer. To enable this in a type-safe manner, we chose [Hono](https://hono.dev) as the API server and client library. + + + Hono is a small, simple, and ultrafast web framework that gives you a way to + define your API endpoints with full type safety. It provides built-in + middleware for common needs like validation, caching, and CORS. + + It also + includes an [RPC client](https://hono.dev/docs/guides/rpc) for making + type-safe function calls from the frontend. Being edge-first, it's optimized + for serverless environments and offers excellent performance. + + +All API endpoints and their resolvers are defined in the `packages/api/` package. Here you will find a `modules` folder that contains the different feature modules of the API. Each module has its own folder and exports all its resolvers. + +For each module, we create a separate Hono route in the `packages/api/index.ts` file and aggregate all sub-routers into one main router. + +The API is then exposed as a route handler that will be provided as a Next.js API route: + +```ts title="apps/web/src/app/api/[...route]/route.ts" +import { handle } from "hono/vercel"; + +import { appRouter } from "@turbostarter/api"; + +const handler = handle(appRouter); +export { + handler as GET, + handler as POST, + handler as OPTIONS, + handler as PUT, + handler as PATCH, + handler as DELETE, + handler as HEAD, +}; +``` + +Learn more about how to use the API in your mobile app in the following sections: diff --git a/.context/turbostarter-framework-context/sections/mobile/auth/2fa.md b/.context/turbostarter-framework-context/sections/mobile/auth/2fa.md new file mode 100644 index 0000000..1ddda6f --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/auth/2fa.md @@ -0,0 +1,131 @@ +--- +title: Two-Factor Authentication (2FA) +description: Add an extra layer of security with two-factor authentication in your mobile app. +url: /docs/mobile/auth/2fa +--- + +# Two-Factor Authentication (2FA) + +TurboStarter uses [Better Auth's 2FA plugin](https://www.better-auth.com/docs/plugins/2fa) to provide multi-factor authentication (MFA) capabilities in your mobile app. Two-factor authentication adds an extra layer of security by requiring users to provide a second form of verification alongside their password. + +## Available methods + +TurboStarter supports multiple 2FA verification methods through Better Auth: + +* **TOTP (Time-based One-Time Password)** - codes generated by authenticator apps +* **OTP (One-Time Password)** - codes sent via email or SMS +* **Backup codes** - single-use recovery codes for account recovery + +You can use any TOTP-compatible authenticator app, such as: + +* [Google Authenticator](https://support.google.com/accounts/answer/1066447) +* [Authy](https://authy.com/) +* [Microsoft Authenticator](https://www.microsoft.com/en-us/security/mobile-authenticator-app) +* [1Password](https://1password.com/features/authenticator/) +* [Bitwarden](https://bitwarden.com/help/authenticator-keys/) + +## Enabling 2FA + + + + ### Enable in settings + + Users enable two-factor authentication in their account security settings within the mobile app. + + ![Enable 2FA](/images/docs/mobile/auth/two-factor/enable.png) + + + + ### Setup authenticator + + A QR code is displayed in the mobile app for users to scan with their authenticator app. Users can also manually enter the setup key if needed. + + ![Setup authenticator](/images/docs/mobile/auth/two-factor/authenticator-app.png) + + + + ### Verify setup + + Users enter a verification code from their authenticator to confirm setup directly in the mobile app. + + + + ### Backup codes + + Users receive single-use backup codes for account recovery, which can be saved or shared from the mobile app. + + ![Backup codes](/images/docs/mobile/auth/two-factor/backup-codes.png) + + + + + Recovery codes are essential for account recovery if users lose access to + their authenticator device. Make sure to educate users about safely storing + their backup codes, and consider providing options to save them to the device + or share them securely. + + +## Using 2FA + + + + ### Sign in normally + + Users enter their email and password or use other authentication methods (biometric, social login) as usual in the mobile app. + + + + ### 2FA prompt + + After successful password verification, users are prompted for their 2FA code in a native mobile interface. + + ![2FA prompt](/images/docs/mobile/auth/two-factor/sign-in-prompt.png) + + + + ### Enter verification code + + Users input the 6-digit code from their authenticator app using the mobile keyboard. + + + + ### Access granted + + Upon successful verification, users gain access to their account and are navigated to the main app screen. + + + +### Trusted devices + +Users can mark their mobile device as trusted during 2FA verification. Trusted devices won't require 2FA verification for 60 days, providing a balance between security and convenience. This is particularly useful for personal mobile devices. + +## Mobile-specific considerations + +### Biometric integration + +On mobile devices, 2FA can be enhanced with biometric authentication (fingerprint, face recognition) for added security and convenience. + +### App switching + +The mobile app should handle switching between your app and authenticator apps seamlessly, maintaining the authentication state when users return. + +### Offline support + +Consider implementing offline backup code verification for scenarios where users may have limited connectivity. + +### Push notifications + +For OTP delivery via SMS or email, ensure your app handles incoming notifications gracefully during the authentication flow. + +## Configuration + +2FA is configured through Better Auth's plugin system. The plugin handles: + +* Secure secret generation and storage +* QR code generation for authenticator setup +* TOTP code validation +* Backup code generation and management +* Trusted device management +* Mobile-specific session handling + +For detailed implementation instructions, refer to the [Better Auth 2FA documentation](https://www.better-auth.com/docs/plugins/2fa). diff --git a/.context/turbostarter-framework-context/sections/mobile/auth/configuration.md b/.context/turbostarter-framework-context/sections/mobile/auth/configuration.md new file mode 100644 index 0000000..a81f679 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/auth/configuration.md @@ -0,0 +1,122 @@ +--- +title: Configuration +description: Configure authentication for your application. +url: /docs/mobile/auth/configuration +--- + +# Configuration + +TurboStarter supports multiple different authentication methods: + +* **Password** - the traditional email/password method +* **Magic Link** - passwordless email link authentication +* **Anonymous** - guest mode for users who want to proceed anonymously +* **OAuth** - OAuth providers, [Apple](https://www.better-auth.com/docs/authentication/apple), [Google](https://www.better-auth.com/docs/authentication/google) and [Github](https://www.better-auth.com/docs/authentication/github) are set up by default + +All authentication methods are enabled by default, but you can easily customize them to your needs. You can enable or disable any method, and configure them according to your requirements. + + + Remember that you can mix and match these methods or add new ones - for + example, you can have both password and magic link authentication enabled at + the same time, giving your users more flexibility in how they authenticate. + + +Authentication configuration can be customized through a simple configuration file. The following sections explain the available options and how to configure each authentication method based on your requirements. + +## API + +To enable new authentication method or add some plugin, you'd need to update the API configuration. Please refer to [web authentication configuration](/docs/web/auth/configuration) for more information as it's not strictly related with mobile app configuration. + + + For mobile apps, we need to define an [authentication trusted origin](https://www.better-auth.com/docs/reference/security#trusted-origins) using a mobile app scheme instead. + + App schemes (like `turbostarter://`) are used for [deep linking](https://docs.expo.dev/guides/linking/) users to specific screens in your app after authentication. + + To find your app scheme, take a look at `apps/mobile/app.config.ts` file and then add it to your auth server configuration: + + ```ts title="server.ts" + export const auth = betterAuth({ + ... + + trustedOrigins: ["turbostarter://**"], + + ... + }); + ``` + + Adding your app scheme to the trusted origins list is crucial for security - it prevents CSRF attacks and blocks malicious open redirects by ensuring only requests from approved origins (your app) are allowed through. + + [Read more about auth security in Better Auth's documentation.](https://www.better-auth.com/docs/reference/security) + + +## UI + +We have separate configuration that determines what is displayed to your users in the **UI**. It's set at `apps/mobile/config/auth.ts`. + +The recommendation is to **not update this directly** - instead, please define the environment variables and override the default behavior. + +```ts title="apps/mobile/config/auth.ts" +import env from "env.config"; +import { Platform } from "react-native"; + +import { SocialProvider, authConfigSchema } from "@turbostarter/auth"; + +import type { AuthConfig } from "@turbostarter/auth"; + +export const authConfig = authConfigSchema.parse({ + providers: { + password: env.EXPO_PUBLIC_AUTH_PASSWORD, + magicLink: env.EXPO_PUBLIC_AUTH_MAGIC_LINK, + anonymous: env.EXPO_PUBLIC_AUTH_ANONYMOUS, + oAuth: [ + Platform.select({ + android: SocialProvider.GOOGLE, + ios: SocialProvider.APPLE, + }), + SocialProvider.GITHUB, + ], + }, +}) satisfies AuthConfig; +``` + +The configuration is also validated using the Zod schema, so if something is off, you'll see the errors. + +For example, if you want to switch from password to magic link, you'd change the following environment variables: + +```dotenv title=".env.local" +EXPO_PUBLIC_AUTH_PASSWORD=false +EXPO_PUBLIC_AUTH_MAGIC_LINK=true +``` + +To display third-party providers in the UI, you need to set the `oAuth` array to include the provider you want to display. The default is Google and Github. + +```tsx title="apps/web/config/auth.ts" +providers: { + ... + oAuth: [ + Platform.select({ + android: SocialProvider.GOOGLE, + ios: SocialProvider.APPLE, + }), + SocialProvider.GITHUB, + ], + ... +}, +``` + +You can even display specific providers for specific platforms - for example, you can display Google authentication for Android and Apple authentication for iOS. + +## Third party providers + +To enable third-party authentication providers, you'll need to: + +1. Set up an OAuth application in the provider's developer console (like [Apple Developer Portal](https://developer.apple.com/account/), [Google Cloud Console](https://console.cloud.google.com/), [Github Developer Settings](https://github.com/settings/developers) or any other provider you want to use) +2. Configure the corresponding environment variables in your TurboStarter **API (web) application** + +Each OAuth provider requires its own set of credentials and environment variables. Please refer to the [Better Auth documentation](https://better-auth.com/docs/concepts/oauth) for detailed setup instructions for each supported provider. + + + Make sure to set both development and production environment variables + appropriately. Your OAuth provider may require different callback URLs for + each environment. + diff --git a/.context/turbostarter-framework-context/sections/mobile/auth/flow.md b/.context/turbostarter-framework-context/sections/mobile/auth/flow.md new file mode 100644 index 0000000..e49d68c --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/auth/flow.md @@ -0,0 +1,51 @@ +--- +title: User flow +description: Discover the authentication flow in Turbostarter. +url: /docs/mobile/auth/flow +--- + +# User flow + +TurboStarter ships with a fully functional authentication system. Most of the screens and components are preconfigured and easily customizable to your needs. + +Here you will find a quick walkthrough of the authentication flow. + +## Sign up + +The sign-up screen is where users can create an account. They need to provide their email address and password. + +![Sign up](/images/docs/mobile/auth/sign-up.png) + +Once successful, users are asked to confirm their email address. This is enabled by default - and due to security reasons, it's not possible to disable it. + + + Make sure to configure the [email provider](/docs/web/emails/configuration) together with the [auth hooks](/docs/web/emails/sending#authentication-emails) to be able to send emails from your app. + + +![Confirm email](/images/docs/mobile/auth/confirm-email.png) + +## Sign in + +The sign-in screen is where users can log in to their account. They need to provide their email address and password, use magic link (if enabled) or third-party providers. + +![Sign in](/images/docs/mobile/auth/sign-in.png) + +## Sign out + +The sign out button is located in the user account settings. + +![Settings](/images/docs/mobile/auth/settings.png) + +## Forgot password + +The forgot password screen is where users can reset their password. They need to provide their email address and follow the instructions in the email. + +It comes together with the reset password screen, where users land from a forgot email. There they can reset their password by providing new password and confirming it. + +![Forgot password](/images/docs/mobile/auth/forgot-password.png) + +## Two-factor authentication + +Two-factor authentication is a security feature that requires users to provide a code sent to their email or phone number in addition to their password when logging in. + +![Two-factor authentication](/images/docs/mobile/auth/two-factor/sign-in-prompt.png) diff --git a/.context/turbostarter-framework-context/sections/mobile/auth/oauth.md b/.context/turbostarter-framework-context/sections/mobile/auth/oauth.md new file mode 100644 index 0000000..dbadffa --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/auth/oauth.md @@ -0,0 +1,72 @@ +--- +title: OAuth +description: Get started with social authentication. +url: /docs/mobile/auth/oauth +--- + +# OAuth + +Better Auth supports almost **30** (!) different [OAuth providers](https://www.better-auth.com/docs/concepts/oauth). They can be easily configured and enabled in the kit without any additional configuration needed. + + + TurboStarter provides you with all the configuration required to handle OAuth providers responses from your app: + + * redirects + * middleware + * confirmation API routes + + You just need to configure one of the below providers on their side and set correct credentials as environment variables in your TurboStarter app. + + +![OAuth providers](/images/docs/web/auth/social-providers.png) + +Third Party providers need to be configured, managed and enabled fully on the provider's side. TurboStarter just needs the correct credentials to be set as environment variables in your app and passed to the [authentication API configuration](/docs/web/auth/configuration#api). + +To enable OAuth providers in your TurboStarter app, you need to: + +1. Set up an OAuth application in the provider's developer console (like [Apple Developer Portal](https://developer.apple.com/account/), [Google Cloud Console](https://console.cloud.google.com/), [Github Developer Settings](https://github.com/settings/developers) or any other provider you want to use) +2. Configure the provider's credentials as environment variables in your app. For example, for Google OAuth: + +```dotenv title="apps/web/.env.local" +GOOGLE_CLIENT_ID= +GOOGLE_CLIENT_SECRET= +``` + +Then, pass it to the authentication configuration in `packages/auth/src/server.ts`: + +```ts title="server.ts" +export const auth = betterAuth({ + ... + + socialProviders: { + [SocialProvider.GOOGLE]: { + clientId: env.GOOGLE_CLIENT_ID, + clientSecret: env.GOOGLE_CLIENT_SECRET, + }, + }, + + ... +}); +``` + + + For mobile apps, we need to define a trusted origin using an app scheme instead of a classic URL. App schemes (like `turbostarter://`) are used for [deep linking](https://docs.expo.dev/guides/linking/) users to specific screens in your app after authentication. + + To find your app scheme, take a look at `apps/mobile/app.config.ts` file and then add it to your auth server configuration: + + ```ts title="server.ts" + export const auth = betterAuth({ + ... + + trustedOrigins: ["turbostarter://**"], + + ... + }); + ``` + + Adding your app scheme to the trusted origins list is crucial for security - it prevents CSRF attacks and blocks malicious open redirects by ensuring only requests from approved origins (your app) are allowed through. + + [Read more about auth security in Better Auth's documentation.](https://www.better-auth.com/docs/reference/security) + + +Also, we included some native integrations (["Sign in with Apple"](/docs/mobile/auth/oauth/apple) for iOS and ["Sign in with Google"](/docs/mobile/auth/oauth/google) for Android) to make the sign-in process smoother and faster for the user. diff --git a/.context/turbostarter-framework-context/sections/mobile/auth/oauth/apple.md b/.context/turbostarter-framework-context/sections/mobile/auth/oauth/apple.md new file mode 100644 index 0000000..fcd87a0 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/auth/oauth/apple.md @@ -0,0 +1,74 @@ +--- +title: Apple +description: Configure "Sign in with Apple" for your mobile application. +url: /docs/mobile/auth/oauth/apple +--- + +# Apple + +**"Sign in with Apple"** provides a native, privacy-preserving SSO experience on iOS. Use the system Apple button and the Apple Authentication APIs to sign users in, then verify the identity token on your backend and create a session with your auth server. + + + Native Apple ID authentication is available on iOS only. You are advised to + present the official system button (or our custom component - also compliant!) + and follow [Apple's Human Interface + Guidelines](https://developer.apple.com/design/human-interface-guidelines/sign-in-with-apple) + for best practices. + + +![Sign in with Apple](/images/docs/mobile/auth/sign-in-with-apple.png) + +## Why use native Apple ID authentication? + + + + System sheet + official button, aligned with [Apple's Human Interface Guidelines](https://developer.apple.com/design/human-interface-guidelines/sign-in-with-apple) for trust and conversion. + + + + Private relay email and limited data by design, ensuring your users' privacy is protected and compliant with App Store guidelines. + + + + Fast, low-friction sign-in on iOS enabling your users to sign in without the need to remember or create additional passwords. + + + + JWT verification on the server with [Better Auth](https://www.better-auth.com/docs/authentication/apple), keeping your users' credentials secure. + + + + We exchange Apple credentials for an app session and persist it in the app. + + + +## Requirements + +* Enable the "Sign in with Apple" capability for your bundle identifier in the [Apple Developer Portal](https://developer.apple.com/account/resources/identifiers/list) +* Add the entitlement and build with [EAS](/docs/mobile/publishing/checklist) (or configure natively) +* Ensure your app's deep link scheme is added to the auth server's [trusted origins configuration](/docs/mobile/auth/configuration) + +Check the [Better Auth documentation](https://www.better-auth.com/docs/authentication/apple) for more details on how to configure all the required keys and certificates. + +## High-level flow + +1. Check availability with `AppleAuthentication.isAvailableAsync()`. +2. Render the system `AppleAuthenticationButton` or custom TurboStarter component. +3. Call `AppleAuthentication.signInAsync()` requesting `FULL_NAME` and/or `EMAIL` as needed. +4. Send the returned `idTokeb` identifier to the API powered by [Better Auth](https://www.better-auth.com/docs/authentication/apple) to verify and establish a session. +5. Optionally track credential state with `AppleAuthentication.getCredentialStateAsync(user)`. + + + Always verify the JWT signature from `idToken` on your backend using Apple's + public keys before creating a session. + + +For a more in-depth overview of Apple ID authentication—including implementation details, platform caveats, and advanced configuration—see the following resources: + + + + + + + + diff --git a/.context/turbostarter-framework-context/sections/mobile/auth/oauth/google.md b/.context/turbostarter-framework-context/sections/mobile/auth/oauth/google.md new file mode 100644 index 0000000..f0d2b13 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/auth/oauth/google.md @@ -0,0 +1,71 @@ +--- +title: Google +description: Configure "Sign in with Google" for your mobile application. +url: /docs/mobile/auth/oauth/google +--- + +# Google + +**"Sign in with Google"** enables a fast account-chooser experience on mobile (especially on Android). Configure your platform credentials, prompt the native account picker, then exchange the returned token on your backend to create a session with your auth server. + + + On Android, Google Sign‑In uses [Google Identity + Services](https://developers.google.com/identity?hl=pl) and integrates with + the system account chooser. On iOS, the recommended Expo flow uses + [expo-auth-session](https://docs.expo.dev/versions/latest/sdk/auth-session/) + with Google for a native, web-based sign-in experience. + + +![Sign in with Google](/images/docs/mobile/auth/sign-in-with-google.png) + +## Why use Google authentication? + + + + Account picker and token storage integrated with the OS for speed and familiarity. + + + + Android native chooser; iOS polished experience via Expo. + + + + Tokens are verified server-side with [Better Auth](https://www.better-auth.com/docs/authentication/google) before a session is issued. + + + + Reduce friction with one-tap sign-in and fewer passwords to remember. + + + + Built on [Google Identity Services](https://developers.google.com/identity?hl=pl) and best-practice OAuth flows. + + + +## Requirements + +* Configure [Google Cloud OAuth Client IDs](https://react-native-google-signin.github.io/docs/setting-up/get-config-file) (Android package + SHA-1, iOS bundle ID) in the [Google Cloud Console](https://console.cloud.google.com/) +* Build with [EAS](/docs/mobile/publishing/checklist) to ensure native credentials are embedded correctly +* Add your app deep link scheme to the auth server's [trusted origins configuration](/docs/mobile/auth/configuration) + +Check the [Better Auth documentation](https://www.better-auth.com/docs/authentication/google) and [`@react-native-google-signin/google-signin` documentation](https://react-native-google-signin.github.io) for steps to configure your server verification, client IDs and more. + +## High-level flow + +1. Configure Google OAuth Client IDs for Android and iOS in [Google Cloud Console](https://console.cloud.google.com/). +2. Initialize the Google auth request in your app and render a "Sign in with Google" button. +3. Prompt the account chooser; on success you receive an `idToken` and/or `accessToken`. +4. Send the tokens to the API powered by [Better Auth](https://www.better-auth.com/docs/authentication/google) to verify and establish a session. +5. Persist the session and proceed to the app. + +For a more in-depth overview of Google authentication, including implementation details, platform caveats, and advanced configuration, see the following resources: + + + + + + + + + + diff --git a/.context/turbostarter-framework-context/sections/mobile/auth/overview.md b/.context/turbostarter-framework-context/sections/mobile/auth/overview.md new file mode 100644 index 0000000..9b61ccd --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/auth/overview.md @@ -0,0 +1,40 @@ +--- +title: Overview +description: Get started with authentication. +url: /docs/mobile/auth/overview +--- + +# Overview + +TurboStarter uses [Better Auth](https://better-auth.com) to handle authentication. It's a secure, production-ready authentication solution that integrates seamlessly with many frameworks and provides enterprise-grade security out of the box. + + + One of the core principles of TurboStarter is to do things **as simple as possible**, and to make everything **as performant as possible**. + + Better Auth provides an excellent developer experience with minimal configuration required, while maintaining enterprise-grade security standards. Its framework-agnostic approach and focus on performance makes it the perfect choice for TurboStarter. + + Recently, Better Auth [announced](https://www.better-auth.com/blog/authjs-joins-better-auth) an incorporation of [Auth.js (27k+ stars on Github)](https://authjs.dev/), making it even more powerful and flexible. + + +![Better Auth](/images/docs/better-auth.png) + +You can read more about Better Auth in the [official documentation](https://better-auth.com/docs). + +TurboStarter supports multiple authentication methods: + +* **Password** - the traditional email/password method +* **Magic Link** - magic links with [deep linking](https://docs.expo.dev/linking/overview) +* **Anonymous** - allowing users to proceed anonymously +* **OAuth** - OAuth social providers ([Apple](https://www.better-auth.com/docs/authentication/apple), [Google](https://www.better-auth.com/docs/authentication/google) and [Github](https://www.better-auth.com/docs/authentication/github) preconfigured) +* **Native Apple authentication** - ["Sign in with Apple"](/docs/mobile/auth/oauth/apple) for iOS +* **Native Google authentication** - ["Sign in with Google"](/docs/mobile/auth/oauth/google) for Android + +As well as common applications flows, with ready-to-use views and components: + +* **Sign in** - sign in with email/password or OAuth providers +* **Sign up** - sign up with email/password or OAuth providers +* **Sign out** - sign out +* **Password recovery** - forgot and reset password +* **Email verification** - verify email + +You can construct your auth flow like LEGO bricks - plug in needed parts and customize them to your needs. diff --git a/.context/turbostarter-framework-context/sections/mobile/billing.md b/.context/turbostarter-framework-context/sections/mobile/billing.md new file mode 100644 index 0000000..1aafa0e --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/billing.md @@ -0,0 +1,72 @@ +--- +title: Billing +description: Get started with billing in TurboStarter. +url: /docs/mobile/billing +--- + +# Billing + + + For now, billing has a limited functionalities on mobile, we're mostly relying on the [web app](/docs/web/billing/overview) to handle billing. + + We are working on a fully-featured mobile billing to help you monetize your mobile app easier. Stay tuned for updates. + + [See roadmap](https://github.com/orgs/turbostarter/projects/1) + + +## Fetching customer data + +When your user purchased a plan from your landing page or web app, you can easily fetch their data using the [API](/docs/mobile/api/client). + +To do so, just call the `/api/billing/customer` endpoint: + +```tsx title="customer-screen.tsx" +import { api } from "~/lib/api"; + +export default function CustomerScreen() { + const { data: customer, isLoading } = useQuery({ + queryKey: ["customer"], + queryFn: handle(api.billing.customer.$get), + }); + + if (isLoading) return Loading...; + + return {customer?.plan}; +} +``` + +You may also want to ensure that user is logged in before fetching their billing data to avoid unnecessary API calls. + +```tsx title="customer-screen.tsx" +import { api } from "~/lib/api"; +import { authClient } from "~/lib/auth"; + +export default function CustomerScreen() { + const { + data: { user }, + } = authClient.useSession(); + + const { data: customer } = useQuery({ + queryKey: ["customer"], + queryFn: handle(api.billing.customer.$get), + enabled: !!user, // [!code highlight] + }); + + if (!user || !customer) { + return null; + } + + return ( + + {user.email} + {customer.plan} + + ); +} +``` + + + Be mindful when implementing payment-related features in your mobile app. Apple has strict guidelines regarding external payment systems and **may reject your app** if you aggressively redirect users to web-based payment flows. Make sure to review the [App Store Review Guidelines](https://developer.apple.com/app-store/review/guidelines/#payments) carefully and consider implementing native in-app purchases for iOS users to ensure compliance. + + We are currently working on a fully native payments system that will make it easier to comply with Apple's guidelines - stay tuned for updates! + diff --git a/.context/turbostarter-framework-context/sections/mobile/cli.md b/.context/turbostarter-framework-context/sections/mobile/cli.md new file mode 100644 index 0000000..8afa1cd --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/cli.md @@ -0,0 +1,92 @@ +--- +title: CLI +description: Start your new project with a single command. +url: /docs/mobile/cli +--- + +# CLI + + + +To help you get started with TurboStarter **as quickly as possible**, we've developed a [CLI](https://www.npmjs.com/package/@turbostarter/cli) that enables you to create a new project (with all the configuration) in seconds. + +The CLI is a set of commands that will help you create a new project, generate code, and manage your project efficiently. + +Currently, the following action is available: + +* **Starting a new project** - Generate starter code for your project with all necessary configurations in place (billing, database, emails, etc.) + +**The CLI is in beta**, and we're actively working on adding more commands and actions. Soon, the following features will be available: + +* **Translations** - Translate your project, verify translations, and manage them effectively +* **Installing plugins** - Easily install plugins for your project +* **Dynamic code generation** - Generate dynamic code based on your project structure + +## Installation + +You can run commands using `npx`: + +```bash +npx turbostarter +npx @turbostarter/cli@latest +``` + + + If you don't want to install the CLI globally, you can simply replace the examples below with `npx @turbostarter/cli@latest` instead of `turbostarter`. + + This also allows you to always run the latest version of the CLI without having to update it. + + +## Usage + +Running the CLI without any arguments will display the general information about the CLI: + +```bash +Usage: turbostarter [options] [command] + +Your Turbo Assistant for starting new projects, adding plugins and more. + +Options: + -v, --version display the version number + -h, --help display help for command + +Commands: + new create a new TurboStarter project + help [command] display help for command +``` + +You can also display help for it or check the actual version. + +### Starting a new project + +Use the `new` command to initialize configuration and dependencies for a new project. + +```bash +npx turbostarter new +``` + +You will be asked a few questions to configure your project: + +```bash +✔ All prerequisites are satisfied, let's start! 🚀 + +? What do you want to ship? › + ◉ Web app + ◉ Mobile app + ◯ Browser extension +? Enter your project name. › +? How do you want to use database? › + Local (powered by Docker) + Cloud +? What do you want to use for billing? › + Stripe + Lemon Squeezy + +... + +🎉 You can now get started. Open the project and just ship it! 🎉 + +Problems? https://www.turbostarter.dev/docs +``` + +It will create a new project, configure providers, install dependencies and start required services in development mode. diff --git a/.context/turbostarter-framework-context/sections/mobile/configuration/app.md b/.context/turbostarter-framework-context/sections/mobile/configuration/app.md new file mode 100644 index 0000000..92528a8 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/configuration/app.md @@ -0,0 +1,152 @@ +--- +title: App configuration +description: Learn how to setup the overall settings of your app. +url: /docs/mobile/configuration/app +--- + +# App configuration + +When configuring your app, you'll need to define settings in different places depending on which provider will use them (e.g., Expo, EAS). + +## App configuration + +Let's start with the core settings for your app. These settings are **crucial** as they're used by Expo and EAS to build your app, determine its store presence, prepare updates, and more. + +This configuration includes essential details like the official name, description, scheme, store IDs, splash screen configuration, and more. + +You'll define these settings in `apps/mobile/app.config.ts`. Make sure to follow the [Expo config schema](https://docs.expo.dev/versions/latest/config/app/) when setting this up. + +Here is an example of what the config file looks like: + +```ts title="apps/mobile/app.config.ts" +import { ExpoConfig } from "expo/config"; + +export default ({ config }: ConfigContext): ExpoConfig => ({ + ...config, + name: "TurboStarter", + slug: "turbostarter", + scheme: "turbostarter", + version: "0.1.0", + orientation: "portrait", + icon: "./assets/images/icon.png", + userInterfaceStyle: "automatic", + assetBundlePatterns: ["**/*"], + sdkVersion: "51.0.0", + platforms: ["ios", "android"], + updates: { + fallbackToCacheTimeout: 0, + }, + newArchEnabled: true, + ios: { + bundleIdentifier: "your.bundle.identifier", + supportsTablet: false, + }, + android: { + package: "your.bundle.identifier", + adaptiveIcon: { + monochromeImage: "./public/images/icon/android/monochrome.png", + foregroundImage: "./public/images/icon/android/adaptive.png", + backgroundColor: "#0D121C", + }, + }, + extra: { + eas: { + projectId: "your-project-id", + }, + }, + experiments: { + tsconfigPaths: true, + typedRoutes: true, + }, + plugins: ["expo-router", ["expo-splash-screen", SPLASH]], +}); +``` + +Make sure to replace the values with your own and take your time to set everything correctly. + + + +### Internal configuration + +The same as for the [web app](/docs/web/configuration/app), and [extension](/docs/extension/configuration/app), we're defining the internal app config, which stores some overall variables for your application (that can't be read from Expo config). + +The recommendation is to **not update this directly** - instead, please define the environment variables and override the default behavior. The configuration is strongly typed so you can use it safely accross your codebase - it'll be validated at build time. + +```ts title="apps/mobile/src/config/app.ts" +import env from "env.config"; + +export const appConfig = { + locale: env.EXPO_PUBLIC_DEFAULT_LOCALE, + url: env.EXPO_PUBLIC_SITE_URL, + theme: { + mode: env.EXPO_PUBLIC_THEME_MODE, + color: env.EXPO_PUBLIC_THEME_COLOR, + }, +} as const; +``` + +For example, to set the mobile app default theme color, you'd update the following variable: + +```dotenv title=".env.local" +EXPO_PUBLIC_THEME_COLOR="yellow" +``` + + + Do NOT use `process.env` to get the values of the variables. Variables + accessed this way are not validated at build time, and thus the wrong variable + can be used in production. + + +## EAS configuration + +To properly build and publish your app, you need to define settings for the EAS build service. + +This is done in `apps/mobile/eas.json` and it must follow the [EAS config scheme](https://docs.expo.dev/eas/json/). + +Here is an example of what the config file looks like: + +```json title="apps/mobile/eas.json" +{ + "cli": { + "version": ">= 4.1.2" + }, + "build": { + "base": { + "node": "20.15.0", + "pnpm": "9.6.0", + "ios": { + "resourceClass": "m-medium" + }, + "env": { + "EXPO_PUBLIC_DEFAULT_LOCALE": "en", + "EXPO_PUBLIC_AUTH_PASSWORD": "true", + "EXPO_PUBLIC_AUTH_MAGIC_LINK": "false", + "EXPO_PUBLIC_THEME_MODE": "system", + "EXPO_PUBLIC_THEME_COLOR": "orange" + } + }, + ... + "preview": { + "extends": "base", + "distribution": "internal", + "android": { + "buildType": "apk" + }, + "env": { + "APP_ENV": "test", + } + }, + "production": { + "extends": "base", + "env": { + "APP_ENV": "production", + } + } + ... + }, +} +``` + +Make sure to also fill all the [environment variables](/docs/mobile/configuration/environment-variables) with the correct values for your project and correct environment, otherwise your app won't build and you won't be able to publish it. + + diff --git a/.context/turbostarter-framework-context/sections/mobile/configuration/environment-variables.md b/.context/turbostarter-framework-context/sections/mobile/configuration/environment-variables.md new file mode 100644 index 0000000..86767ad --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/configuration/environment-variables.md @@ -0,0 +1,92 @@ +--- +title: Environment variables +description: Learn how to configure environment variables. +url: /docs/mobile/configuration/environment-variables +--- + +# Environment variables + +Environment variables are defined in the `.env` file in the root of the repository and in the root of the `apps/mobile` package. + +* **Shared environment variables**: Defined in the **root** `.env` file. These are shared between environments (e.g., development, staging, production) and apps (e.g., web, mobile). +* **Environment-specific variables**: Defined in `.env.development` and `.env.production` files. These are specific to the development and production environments. +* **App-specific variables**: Defined in the app-specific directory (e.g., `apps/web`). These are specific to the app and are not shared between apps. +* **Build environment variables**: Not stored in the `.env` file. Instead, they are stored in `eas.json` file used to build app on [Expo Application Services](https://expo.dev/eas). +* **Secret keys**: They're not stored on mobile side, instead [they're defined on the web side.](/docs/web/configuration/environment-variables#secret-keys) + +## Shared variables + +Here you can add all the environment variables that are shared across all the apps. + +To override these variables in a specific environment, please add them to the specific environment file (e.g. `.env.development`, `.env.production`). + +```dotenv title=".env.local" +# Shared environment variables + +# The database URL is used to connect to your database. +DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres" + +# The name of the product. This is used in various places across the apps. +PRODUCT_NAME="TurboStarter" + +# The url of the web app. Used mostly to link between apps. +URL="http://localhost:3000" + +... +``` + +## App-specific variables + +Here you can add all the environment variables that are specific to the app (e.g. `apps/mobile`). + +You can also override the shared variables defined in the root `.env` file. + +```dotenv title="apps/mobile/.env.local" +# App-specific environment variables + +# Env variables extracted from shared to be exposed to the client in Expo app +EXPO_PUBLIC_SITE_URL="${URL}" +EXPO_PUBLIC_DEFAULT_LOCALE="${DEFAULT_LOCALE}" + +# Theme mode and color +EXPO_PUBLIC_THEME_MODE="system" +EXPO_PUBLIC_THEME_COLOR="orange" + +# Use this variable to enable or disable password-based authentication. If you set this to true, users will be able to sign up and sign in using their email and password. If you set this to false, the form won't be shown. +EXPO_PUBLIC_AUTH_PASSWORD="true" + +... +``` + + + To make environment variables available in the Expo app code, you need to prefix them with `EXPO_PUBLIC_`. They will be injected to the code during the build process. + + Only environment variables prefixed with `EXPO_PUBLIC_` will be injected. + + [Read more about Expo environment variables.](https://docs.expo.dev/guides/environment-variables/) + + +## Build environment variables + +To allow your app to build properly on [EAS](https://expo.dev/eas) you need to define your environment variables either in your `eas.json` file under corresponding profile (e.g. `preview` or `production`) or directly in the [EAS platform](https://docs.expo.dev/eas/environment-variables/): + +![EAS environment variables](/images/docs/mobile/eas-environment-variables.png) + +Then, when you trigger build, correct environment variables will be injected to your mobile app code ensuring that everything is working correctly. + +[Check EAS documentation for more details.](https://docs.expo.dev/eas/environment-variables/) + +## Secret keys + +Secret keys and sensitive information are to be **never** stored on the mobile app code. + + + It means that you will need to add the secret keys to the **web app, where the API is deployed.** + + The mobile app should only communicate with the backend API, which is typically part of the web app. The web app is responsible for handling sensitive operations and storing secret keys securely. + + [See web documentation for more details.](/docs/web/configuration/environment-variables#secret-keys) + + This is not a TurboStarter-specific requirement, but a best practice for security for any + application. Ultimately, it's your choice. + diff --git a/.context/turbostarter-framework-context/sections/mobile/configuration/paths.md b/.context/turbostarter-framework-context/sections/mobile/configuration/paths.md new file mode 100644 index 0000000..e026908 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/configuration/paths.md @@ -0,0 +1,47 @@ +--- +title: Paths configuration +description: Learn how to configure the paths of your app. +url: /docs/mobile/configuration/paths +--- + +# Paths configuration + +The paths configuration is set at `apps/mobile/config/paths.ts`. This configuration stores all the paths that you'll be using in your application. It is a convenient way to store them in a central place rather than scatter them in the codebase using magic strings. + +It is **unlikely you'll need to change** this unless you're heavily editing the codebase. + +```ts title="apps/mobile/config/paths.ts" +const pathsConfig = { + index: "/", + setup: { + welcome: "/welcome", + auth: { + login: `${AUTH_PREFIX}/login`, + register: `${AUTH_PREFIX}/register`, + forgotPassword: `${AUTH_PREFIX}/password/forgot`, + updatePassword: `${AUTH_PREFIX}/password/update`, + error: `${AUTH_PREFIX}/error`, + join: `${AUTH_PREFIX}/join`, + }, + steps: { + start: `${STEPS_PREFIX}/start`, + required: `${STEPS_PREFIX}/required`, + skip: `${STEPS_PREFIX}/skip`, + final: `${STEPS_PREFIX}/final`, + }, + }, + dashboard: { + user: { + index: DASHBOARD_PREFIX, + ai: `${DASHBOARD_PREFIX}/ai`, + ... + } + ... + } +} as const; +``` + + + By declaring the paths as constants, we can use them safely throughout the + codebase. There is no risk of misspelling or using magic strings. + diff --git a/.context/turbostarter-framework-context/sections/mobile/customization/add-app.md b/.context/turbostarter-framework-context/sections/mobile/customization/add-app.md new file mode 100644 index 0000000..56a6184 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/customization/add-app.md @@ -0,0 +1,83 @@ +--- +title: Adding apps +description: Learn how to add apps to your Turborepo workspace. +url: /docs/mobile/customization/add-app +--- + +# Adding apps + + + This is an **advanced topic** - you should only follow these instructions if you are sure you want to add a new app to your TurboStarter project within your monorepo and want to keep pulling updates from the TurboStarter repository. + + +In some ways - creating a new repository may be the easiest way to manage your application. However, if you want to keep your application within the monorepo and pull updates from the TurboStarter repository, you can follow these instructions. + +To pull updates into a separate application outside of `mobile` - we can use [git subtree](https://www.atlassian.com/git/tutorials/git-subtree). + +Basically, we will create a subtree at `apps/mobile` and create a new remote branch for the subtree. When we create a new application, we will pull the subtree into the new application. This allows us to keep it in sync with the `apps/mobile` folder. + +To add a new app to your TurboStarter project, you need to follow these steps: + + + + ## Create a subtree + + First, we need to create a subtree for the `apps/mobile` folder. We will create a branch named `mobile-branch` and create a subtree for the `apps/mobile` folder. + + ```bash + git subtree split --prefix=apps/mobile --branch mobile-branch + ``` + + + + ## Create a new app + + Now, we can create a new application in the `apps` folder. + + Let's say we want to create a new app `ai-chat` at `apps/ai-chat` with the same structure as the `apps/mobile` folder (which acts as the template for all new apps). + + ```bash + git subtree add --prefix=apps/ai-chat origin mobile-branch --squash + ``` + + You should now be able to see the `apps/ai-chat` folder with the contents of the `apps/mobile` folder. + + + + ## Update the app + + When you want to update the new application, follow these steps: + + ### Pull the latest updates from the TurboStarter repository + + The command below will update all the changes from the TurboStarter repository: + + ```bash + git pull upstream main + ``` + + ### Push the `mobile-branch` updates + + After you have pulled the updates from the TurboStarter repository, you can split the branch again and push the updates to the mobile-branch: + + ```bash + git subtree split --prefix=apps/mobile --branch mobile-branch + ``` + + Now, you can push the updates to the `mobile-branch`: + + ```bash + git push origin mobile-branch + ``` + + ### Pull the updates to the new application + + Now, you can pull the updates to the new application: + + ```bash + git subtree pull --prefix=apps/ai-chat origin mobile-branch --squash + ``` + + + +That's it! You now have a new application in the monorepo 🎉 diff --git a/.context/turbostarter-framework-context/sections/mobile/customization/add-package.md b/.context/turbostarter-framework-context/sections/mobile/customization/add-package.md new file mode 100644 index 0000000..6530240 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/customization/add-package.md @@ -0,0 +1,100 @@ +--- +title: Adding packages +description: Learn how to add packages to your Turborepo workspace. +url: /docs/mobile/customization/add-package +--- + +# Adding packages + + + This is an **advanced topic** - you should only follow these instructions if you are sure you want to add a new package to your TurboStarter application instead of adding a folder to your application in `apps/mobile` or modify existing packages under `packages`. You don't need to do this to add a new screen or component to your application. + + +To add a new package to your TurboStarter application, you need to follow these steps: + + + + ## Generate a new package + + First, enter the command below to create a new package in your TurboStarter application: + + ```bash + turbo gen package + ``` + + Turborepo will ask you to enter the name of the package you want to create. Enter the name of the package you want to create and press enter. + + If you don't want to add dependencies to your package, you can skip this step by pressing enter. + + The command will have generated a new package under packages named `@turbostarter/`. If you named it `example`, the package will be named `@turbostarter/example`. + + + + ## Export a module from your package + + By default, the package exports a single module using the `index.ts` file. You can add more exports by creating new files in the package directory and exporting them from the `index.ts` file or creating export files in the package directory and adding them to the `exports` field in the `package.json` file. + + ### From `index.ts` file + + The easiest way to export a module from a package is to create a new file in the package directory and export it from the `index.ts` file. + + ```ts title="packages/example/src/module.ts" + export function example() { + return "example"; + } + ``` + + Then, export the module from the `index.ts` file. + + ```ts title="packages/example/src/index.ts" + export * from "./module"; + ``` + + ### From `exports` field in `package.json` + + **This can be very useful for tree-shaking.** Assuming you have a file named `module.ts` in the package directory, you can export it by adding it to the `exports` field in the `package.json` file. + + ```json title="packages/example/package.json" + { + "exports": { + ".": "./src/index.ts", + "./module": "./src/module.ts" + } + } + ``` + + **When to do this?** + + 1. when exporting two modules that don't share dependencies to ensure better tree-shaking. For example, if your exports contains both client and server modules. + 2. for better organization of your package + + For example, create two exports `client` and `server` in the package directory and add them to the `exports` field in the `package.json` file. + + ```json title="packages/example/package.json" + { + "exports": { + ".": "./src/index.ts", + "./client": "./src/client.ts", + "./server": "./src/server.ts" + } + } + ``` + + 1. The `client` module can be imported using `import { client } from '@turbostarter/example/client'` + 2. The `server` module can be imported using `import { server } from '@turbostarter/example/server'` + + + + ## Use the package in your application + + You can now use the package in your application by importing it using the package name: + + ```ts title="apps/mobile/src/app/index.tsx" + import { example } from "@turbostarter/example"; + + console.log(example()); + ``` + + + +Et voilà! You have successfully added a new package to your TurboStarter application. 🎉 diff --git a/.context/turbostarter-framework-context/sections/mobile/customization/components.md b/.context/turbostarter-framework-context/sections/mobile/customization/components.md new file mode 100644 index 0000000..6ae57d0 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/customization/components.md @@ -0,0 +1,119 @@ +--- +title: Components +description: Manage and customize your app components. +url: /docs/mobile/customization/components +--- + +# Components + +For the components part, we're using [react-native-reusables](https://reactnativereusables.com//getting-started/introduction/) for atomic, accessible and highly customizable components. + +> It's like shadcn/ui, but for mobile apps. + + + react-native-reusables is a powerful tool that allows you to generate + pre-designed components with a single command. It's built with Uniwind (like + Tailwind CSS for mobile) and accessibility in mind, it's also highly + customizable. + + +TurboStarter defines two packages that are responsible for the UI part of your app: + +* `@turbostarter/ui` - shared styles, [themes](/docs/mobile/customization/styling#themes) and assets (e.g. icons) +* `@turbostarter/ui-mobile` - pre-built UI mobile components, ready to use in your app + +## Adding a new component + +There are basically two ways to add a new component: + + + + TurboStarter is fully compatible with [react-native-reusables CLI](https://www.npmjs.com/package/@react-native-reusables/cli), so you can generate new components with single command. + + Run the following command from the **root** of your project: + + ```bash + pnpm --filter @turbostarter/ui-mobile ui:add + ``` + + This will launch an interactive command-line interface to guide you through the process of adding a new component where you can pick which component you want to add. + + ```bash + Which components would you like to add? > Space to select. A to toggle all. + Enter to submit. + + ◯ accordion + ◯ alert + ◯ alert-dialog + ◯ aspect-ratio + ◯ avatar + ◯ badge + ◯ button + ◯ calendar + ◯ card + ◯ checkbox + ``` + + Newly created components will appear in the `packages/ui/mobile/src` directory. + + + + You can always copy-paste a component from the [react-native-reusables](https://reactnativereusables.com//getting-started/introduction/) website and modify it to your needs. + + This is possible, because the components are headless and don't need (in most cases) any additional dependencies. + + Copy code from the website, create a new file in the `packages/ui/mobile/src` directory and paste the code into the file. + + + + + Keep in mind that you should always try to keep shared components as atomic as possible. This will make it easier to reuse them and to build specific views by composition. + + E.g. include components like `Button`, `Input`, `Card`, `Dialog` in shared package, but keep specific components like `LoginForm` in your app directory. + + +## Using components + +Each component is a standalone entity which has a separate export from the package. It helps to keep things modular, avoid unnecessary dependencies and make tree-shaking possible. + +To import a component from the UI package, use the following syntax: + +```tsx title="apps/mobile/src/modules/common/my-component.tsx" +// [!code word:card] +import { + Card, + CardContent, + CardHeader, + CardFooter, + CardTitle, + CardDescription, +} from "@turbostarter/ui-mobile/card"; +``` + +Then you can use it to build a component specific to your app: + +```tsx title="apps/mobile/src/modules/common/my-component.tsx" +export function MyComponent() { + return ( + + + My Component + + + My Component Content + + + + + + ); +} +``` + + + Most of the components are the same as for the [web app](/docs/web/customization/components). + + It means that you can basically migrate existing web components to the mobile app with just an import change! + + + diff --git a/.context/turbostarter-framework-context/sections/mobile/customization/styling.md b/.context/turbostarter-framework-context/sections/mobile/customization/styling.md new file mode 100644 index 0000000..d29d95d --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/customization/styling.md @@ -0,0 +1,146 @@ +--- +title: Styling +description: Get started with styling your app. +url: /docs/mobile/customization/styling +--- + +# Styling + +To build the mobile user interface, TurboStarter comes with [Uniwind](https://uniwind.dev/) pre-configured. + + + Uniwind brings Tailwind CSS utilities to React Native. It lets you style with familiar classes while keeping native performance and platform-appropriate primitives. + + +## Tailwind configuration + +In the `packages/ui/shared/src/styles` directory, you will find shared CSS files with Tailwind configuration. To change global styles, edit the files in this folder. + +Here is an example of a shared CSS file that includes the Tailwind CSS configuration: + +```css title="packages/ui/shared/src/styles/globals.css" +@import "tailwindcss"; +@import "./themes.css"; + +@custom-variant dark (&:is(.dark *)); + +:root { + --radius: 0.65rem; +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + ... +} +``` + +For colors, we rely strictly on [CSS Variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties) in [OKLCH](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch) format to allow for easy theme management without the need for any JavaScript. + +Also, each app has its own `globals.css` file, which extends the shared config and allows you to override global styles. + +Here is an example of an app's `globals.css` file: + +```css title="apps/mobile/src/assets/styles/globals.css" +@import "@turbostarter/ui/globals.css"; +@import "uniwind"; + +@theme inline { + --font-sans: "Geist_400Regular"; + --font-sans-medium: "Geist_500Medium"; + --font-sans-semibold: "Geist_600SemiBold"; + --font-sans-bold: "Geist_700Bold"; + --font-mono: "GeistMono_400Regular"; +} +``` + +This keeps a clear separation of concerns and a consistent structure for the Tailwind CSS configuration across apps. + +## Themes + +TurboStarter comes with **9+** predefined themes, which you can use to quickly change the look and feel of your app. + +They're defined in the `packages/ui/shared/src/styles/themes` directory. Each theme is a set of variables that can be overridden: + +```ts title="packages/ui/shared/src/styles/themes/colors/orange.ts" +export const orange = { + light: { + background: [1, 0, 0], + foreground: [0.141, 0.005, 285.823], + card: [1, 0, 0], + "card-foreground": [0.141, 0.005, 285.823], + ... + } +} satisfies ThemeColors; +``` + +Each variable is stored as a [OKLCH](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch) array, which is then converted to a CSS variable at build time (by our custom build script). That way we can ensure full type-safety and reuse themes across different parts of our apps (e.g. use the same theme in emails). + +These variables are consumed across platforms. On mobile, the theme provider injects the shared variables into the app, so Uniwind utility classes like `bg-background` and `text-foreground` resolve correctly. + +Feel free to add your own themes or override the existing ones to match your brand's identity. + +To apply a custom theme to your app, use a `useTheme` hook to modify the config: + +```tsx title="apps/mobile/src/lib/providers/theme.tsx" +import { ThemeColor, ThemeMode } from "@turbostarter/ui"; + +import { useTheme } from "~/modules/common/hooks/use-theme"; + +export const ThemeSwitcher = () => { + const { setConfig } = useTheme(); + + return ( + + setConfig({ mode: ThemeMode.DARK, color: ThemeColor.BLUE }) + } + > + Change the theme to dark blue + + ); +}; +``` + +Under the hood, the `useTheme` hook uses [Uniwind.setTheme](https://docs.uniwind.dev/theming/basics#switch-to-a-specific-theme) and [updateCSSVariables](https://docs.uniwind.dev/theming/update-css-variables) utilities to apply the correct theme to the app together with its variables. + +## Dark mode + +TurboStarter comes with built-in dark mode support. + +Each theme has a corresponding set of dark mode variables, which are used to switch the theme to its dark mode counterpart. + +```ts title="packages/ui/shared/src/styles/themes/colors/orange.ts" +export const orange = { + light: {}, + dark: { + background: [0.141, 0.005, 285.823], + foreground: [0.985, 0, 0], + card: [0.21, 0.006, 285.885], + "card-foreground": [0.985, 0, 0], + ... + } +} satisfies ThemeColors; +``` + +Our custom implementation reads the system color scheme via `useColorScheme` and applies `dark:` variants automatically. With the provider injecting shared variables, dark mode works out of the box. + +You can also define the default theme mode and color in the [app configuration](/docs/mobile/configuration/app). + + + + + + + + diff --git a/.context/turbostarter-framework-context/sections/mobile/database.md b/.context/turbostarter-framework-context/sections/mobile/database.md new file mode 100644 index 0000000..724d703 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/database.md @@ -0,0 +1,39 @@ +--- +title: Database +description: Get started with the database. +url: /docs/mobile/database +--- + +# Database + + + To enable communication between your Expo app and the server in a production environment, the web application with Hono API must be deployed first. + + + + + + + + +As a mobile app uses only client-side code, **there's no way to interact with the database directly**. + +Also, you should avoid any workarounds to interact with the database directly, because it can lead to leaking your database credentials and other security issues. + +## Recommended approach + +You can safely use the [API](/docs/mobile/api/overview) and call the endpoints which will run queries on the database. + +To do this you need to set up the database on the [web, server side](/docs/web/database/overview) and then use the [API client](/docs/mobile/api/client) to interact with it. + +Learn more about its configuration in the web part of the docs, especially in the following sections: + + + + + + + + + + diff --git a/.context/turbostarter-framework-context/sections/mobile/extras.md b/.context/turbostarter-framework-context/sections/mobile/extras.md new file mode 100644 index 0000000..b664ae4 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/extras.md @@ -0,0 +1,66 @@ +--- +title: Extras +description: See what you get together with the code. +url: /docs/mobile/extras +--- + +# Extras + +## Tips and Tricks + +In many places, next to the code you will find some marketing tips, design suggestions, and potential risks. This is to help you build a better product and avoid common pitfalls. + +```tsx title="Hero.tsx" +return ( +
+ {/* 💡 Use something that user can visualize e.g. + "Ship your startup while on the toilet" */} +

Best startup on the world

+
+); +``` + +### Submission tips + +When it comes to mobile app and browser extension, you must submit your product to review from Apple/Google etc. We have some tips for you to make sure your submission goes smoothly. + +```json title="app.json" +{ + "ios": { + "infoPlist": { + /* 🍎 add descriptive justification of using this permission on iOS */ + "NSCameraUsageDescription": "This app uses the camera to scan barcodes on event tickets." + } + } +} +``` + +As well as providing you with the info on how to make your store listings better: + +```json title="package.json" +{ + "manifest": { + /* 💡 Use localized messages to get more visibility in web stores */ + "name": "__MSG_extensionName__", + "default_locale": "en" + } +} +``` + +## Discord community + +We have a Discord community where you can ask questions and share your projects. It's a great place to get help and meet other developers. Check more details at [/discord](/discord). + + + +![Discord](/images/docs/discord.png) + +## 25+ SaaS Ideas + +Not sure what to build? We have a list of **25+** SaaS ideas that you can use to get started 🔥 + +Grouped by category, these ideas are a great way to get inspired and start building your next project. + +Including design, copies, marketing tips and potential risks, this list is a great resource for anyone looking to build a SaaS product. + +![SaaS Ideas](/images/docs/saas-ideas.png) diff --git a/.context/turbostarter-framework-context/sections/mobile/faq.md b/.context/turbostarter-framework-context/sections/mobile/faq.md new file mode 100644 index 0000000..dbb74b8 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/faq.md @@ -0,0 +1,92 @@ +--- +title: FAQ +description: Find answers to common technical questions. +url: /docs/mobile/faq +--- + +# FAQ + +## Why isn't everything hidden and configured with one BIG config file? + +TurboStarter intentionally exposes the underlying code rather than hiding it behind configuration files (like some starters do). This design choice follows our **you own your code** philosophy, giving you full control and flexibility over your codebase. + +While a single config file might seem simpler initially, it often becomes restrictive when you need to customize functionality beyond what the config allows. With direct access to the code, you can modify any part of the system to match your specific requirements. + +## I don't know some technology! Should I buy TurboStarter? + +You should be prepared for a learning curve or consider learning it first. However, TurboStarter will still work for you if you're willing to learn. + +Even without knowing some technologies, you can still use the rest of the features. + +## I don't need mobile app or browser extension, what should I do? + +You can simply ignore the mobile app and browser extension parts of the project. You can remove the `apps/mobile` and `apps/extension` directories from the project. + +The modular nature of TurboStarter allows you to remove parts of the project that you don't need without affecting the rest of the stack. + +## I want to use a different provider for X + +Sure! TurboStarter is designed to be modular, so configuring new provider (e.g. for emails, billing or any other service) is straightforward. You just need to make sure your configuration is compatible with common interface to be able to plug it into the codebase. + +## Will you add more packages in the future? + +Yes, we will keep updating TurboStarter with new packages and features. This kit is designed to be modular, allowing for new features and packages to be added without interfering with your existing code. You can always [update your project](/docs/web/installation/update) to the latest version. + +## Can I use this kit for a non-SaaS project? + +This kit is mainly designed for SaaS projects. If you're building something other than a SaaS, the Next.js SaaS Boilerplate might include features you don't need. You can still use it for non-SaaS projects, but you may need to remove or modify features that are specific to SaaS use cases. + +## Can I use personal accounts only? + +Yes! You can disable team accounts and have personal accounts only by setting a feature flag. + +## Does it set up the production instance for me? + +No, TurboStarter does not set up the production instance for you. This includes setting up databases, Stripe, or any other services you need. TurboStarter does not have access to your Stripe or Resend accounts, so setup on your end is required. TurboStarter provides the codebase and documentation to help you set up your SaaS project. + +## Does the starter include Solito? + +No. Solito will not be included in this repo. It is a great tool if you want to share code between your Next.js and Expo app. However, the main purpose of this repo is not the integration between Next.js and Expo — it's the code splitting of your SaaS platforms into a monorepo. You can utilize the monorepo with multiple apps, and it can be any app such as Vite, Electron, etc. + +Integrating Solito into this repo isn't hard, and there are a few [official templates](https://github.com/nandorojo/solito/tree/master/example-monorepos) by the creators of Solito that you can use as a reference. + +## Does this pattern leak backend code to my client applications? + +No, it does not. The `api` package should only be a production dependency in the Next.js application where it's served. The Expo app, browser extension, and all other apps you may add in the future should only add the `api` package as a dev dependency. This lets you have full type safety in your client applications while keeping your backend code safe. + +If you need to share runtime code between the client and server, you can create a separate `shared` package for this and import it on both sides. + +## How do I get support if I encounter issues? + +For support, you can: + +1. Visit our [Discord](https://discord.gg/KjpK2uk3JP) +2. Contact us via support email ([hello@turbostarter.dev](mailto:hello@turbostarter.dev)) + +## Are there any example projects or demos? + +Yes - feel free to check out our demo app at [demo.turbostarter.dev](https://demo.turbostarter.dev). Also, you can get inspired by projects built by our customers - take a look at [Showcase](/#showcase). + +## How do I deploy my application? + +Please check the [production checklist](/docs/web/deployment/checklist) for more information. + +## How do I update my project when a new version of the boilerplate is released? + +Please read the [documentation for updating your TurboStarter code](/docs/web/installation/update). + +## Can I use the React package X with this kit? + +Yes, you can use any React package with this kit. The kit is based on React, so you are generally only constrained by the underlying technologies and not by the kit itself. Since you own and can edit all the code, you can adapt the kit to your needs. However, if there are limitations with the underlying technology, you might need to work around them. + +## Can I integrate TurboStarter into an existing project? + +TurboStarter is a full-stack starter intended to be used as the foundation of your app. You can copy individual modules or patterns into an existing codebase, but retrofitting the entire starter into a mature project is typically not recommended and is not officially supported. If you choose to copy parts, prefer isolating boundaries (e.g., `packages/` modules) and aligning interfaces first. + +## Where can I deploy my application? + +TurboStarter targets modern Node.js/Next.js runtimes. You can deploy to providers that support these environments, such as [Vercel](/docs/web/deployment/vercel), [Railway](/docs/web/deployment/railway), [Render](/docs/web/deployment/render), [Fly](/docs/web/deployment/fly), or [Netlify](/docs/web/deployment/netlify) - following their Next.js guidance. Review our [production checklist](/docs/web/deployment/checklist) before going live. + +## Can I easily swap providers (billing, email, etc.)? + +Yes. The starter organizes integrations behind clear interfaces so you can replace providers (e.g., billing or email) with minimal surface changes. Keep your implementation behind a module boundary and adapt to the existing types to avoid ripple effects. diff --git a/.context/turbostarter-framework-context/sections/mobile/index.md b/.context/turbostarter-framework-context/sections/mobile/index.md new file mode 100644 index 0000000..db5b659 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/index.md @@ -0,0 +1,336 @@ +--- +title: Introduction +description: Get started with TurboStarter mobile kit. +url: /docs/mobile +--- + +# Introduction + +Welcome to the TurboStarter documentation. This is your starting point for learning about the starter kit, its structure, features, and how to use it for your app development. + + + +## What is TurboStarter? + +TurboStarter is a fullstack starter kit that helps you build scalable and production-ready web apps, mobile apps, and browser extensions in minutes. + +Looking to bootstrap your project quickly? Check out our [TurboStarter CLI guide](/blog/the-only-turbo-cli-you-need-to-start-your-next-project-in-seconds) to get started in seconds. + +## Demo apps + +TurboStarter provides a suite of live demo applications you can try instantly - right in your browser, on your phone, or via browser extensions. Try them live by clicking the buttons below. + + + +## Principles + +TurboStarter is built with the following principles: + +* **As simple as possible** - It should be easy to understand, easy to use, and strongly avoid overengineering things. +* **As few dependencies as possible** - It should have as few dependencies as possible to allow you to take full control over every part of the project. +* **As performant as possible** - It should be fast and light without any unnecessary overhead. + +## Features + +Before diving into the technical details, let's overview the features TurboStarter provides. + +### Multi-platform development + +* [Web](/docs/web/stack): Build web apps with React, Next.js, and Tailwind CSS. +* [Mobile](/docs/mobile/stack): Build mobile apps with React Native and Expo. +* [Browser extension](/docs/extension/stack): Build browser extensions with React and WXT. + +For those interested in AI development, check out our dedicated [TurboStarter AI documentation](/ai/docs) with specialized features for building AI-powered applications. + + + Most features are available on all platforms. You can use the **same codebase** to build web, mobile, and browser extension apps. + + +### Authentication + + + + + + + + + + + + + + + + + + + +### Organizations/teams + + + + + + + + + + + +### Billing + + + + + + + + + + + + + + + +### Database + + + + + + + + + + + +### API + + + + + + + + + + + + + +### Admin + + + + + + + + + + + +### AI + + + + Seamless integration of OpenAI, Anthropic, Groq, Mistral, and Gemini. For more advanced AI features, check out [TurboStarter AI](/ai/docs). + + + + + + + + + +### Internationalization + + + + + + + + + + + +### Emails + + + + + + + + + + + +### Landing page + + + + + + + + + + + + + + + +### Marketing + + + + + + + + + + + + + + + + + +### Storage + + + + + + + +### CMS + + + + + + + +### Theming + + + + + + + + + + + +### Analytics + + + + + + + + + + + +### Monitoring + + + + + + + + + + + +### Deployment + + + + + + + + + + + +### Testing + + + + + + + + + + + +## Use like LEGO blocks + +The biggest advantage of TurboStarter is its modularity. You can use the entire stack or just the parts you need. It's like LEGO blocks - you can build anything you want with it. + +If you don't need a specific feature, feel free to remove it without affecting the rest of the stack. + +This approach allows for: + +* **Easy feature integration** - plug new features into the kit with minimal changes. +* **Simplified maintenance** - keep the codebase clean and maintainable. +* **Core feature separation** - distinguish between core features and custom features. +* **Additional modules** - easily add modules like billing, CMS, monitoring, logger, mailer, and more. + +## Scope of this documentation + +While building a SaaS application involves many moving parts, this documentation focuses specifically on TurboStarter. For in-depth information on the underlying technologies, please refer to their respective official documentation. + +This documentation will guide you through configuring, running, and deploying the kit, and will provide helpful links to the official documentation of technologies where necessary. + +## `llms.txt` + +You can access the entire TurboStarter documentation in Markdown format at [/llms.txt](/llms.txt). This can be used to ask any LLM (assuming it has a big enough context window) questions about the TurboStarter based on the most up-to-date documentation. + +### Example usage + +For instance, to prompt an LLM with questions about the TurboStarter: + +1. Copy the documentation contents from [/llms.txt](/llms.txt) +2. Use the following prompt format: + +``` +Documentation: +{paste documentation here} +--- +Based on the above documentation, answer the following: +{your question} +``` + +## Enjoy! + +This documentation is designed to be easy to follow and understand. If you have any questions or need help, feel free to reach out to us at [hello@turbostarter.dev](mailto:hello@turbostarter.dev). + +Explore new features, build amazing apps, and have fun! 🚀 diff --git a/.context/turbostarter-framework-context/sections/mobile/installation/clone.md b/.context/turbostarter-framework-context/sections/mobile/installation/clone.md new file mode 100644 index 0000000..e00d9f4 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/installation/clone.md @@ -0,0 +1,65 @@ +--- +title: Cloning repository +description: Get the code to your local machine and start developing your app. +url: /docs/mobile/installation/clone +--- + +# Cloning repository + + + Ensure you have Git installed on your local machine before proceeding. You can download Git from [here](https://git-scm.com). + + +## Git clone + +Clone the repository using the following command: + +```bash +git clone git@github.com:turbostarter/core +``` + +By default, we're using [SSH](https://docs.github.com/en/authentication/connecting-to-github-with-ssh) for all Git commands. If you don't have it configured, please refer to the [official documentation](https://docs.github.com/en/authentication/connecting-to-github-with-ssh) to set it up. + +Alternatively, you can use HTTPS to clone the repository: + +```bash +git clone https://github.com/turbostarter/core +``` + +Another alternative could be to use the [Github CLI](https://cli.github.com/) or [Github Desktop](https://desktop.github.com/) for Git operations. + + + +## Git remote + +After cloning the repository, remove the original origin remote: + +```bash +git remote rm origin +``` + +Add the upstream remote pointing to the original repository to pull updates: + +```bash +git remote add upstream git@github.com:turbostarter/core +``` + +Once you have your own repository set up, add your repository as the origin: + +```bash +git remote add origin +``` + + + +## Staying up to date + +To pull updates from the upstream repository, run the following command daily (preferably with your morning coffee ☕): + +```bash +git pull upstream main +``` + +This ensures your repository stays up to date with the latest changes. + +Check [Updating codebase](/docs/web/installation/update) for more details on updating your codebase. diff --git a/.context/turbostarter-framework-context/sections/mobile/installation/commands.md b/.context/turbostarter-framework-context/sections/mobile/installation/commands.md new file mode 100644 index 0000000..d0a8d14 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/installation/commands.md @@ -0,0 +1,353 @@ +--- +title: Common commands +description: Learn about common commands you need to know to work with the mobile project. +url: /docs/mobile/installation/commands +--- + +# Common commands + + + For sure, you don't need these commands to kickstart your project, but it's useful to know they exist for when you need them. + + + + You can set up aliases for these commands in your shell configuration file. For example, you can set up an alias for `pnpm` to `p`: + + ```bash title="~/.bashrc" + alias p='pnpm' + ``` + + Or, if you're using [Zsh](https://ohmyz.sh/), you can add the alias to `~/.zshrc`: + + ```bash title="~/.zshrc" + alias p='pnpm' + ``` + + Then run `source ~/.bashrc` or `source ~/.zshrc` to apply the changes. + + You can now use `p` instead of `pnpm` in your terminal. For example, `p i` instead of `pnpm install`. + + + + To inject environment variables into the command you run, prefix it with `with-env`: + + ```bash + pnpm with-env + ``` + + For example, `pnpm with-env pnpm build` will run `pnpm build` with the environment variables injected. + + Some commands, like `pnpm dev`, automatically inject the environment variables for you. + + +## Installing dependencies + +To install the dependencies, run: + +```bash +pnpm install +``` + +## Starting development server + +Start development server by running: + +```bash +pnpm dev +``` + +## Building project + +To build the project (including all apps and packages), run: + +```bash +pnpm build +``` + +## Building specific app/package + +To build a specific app/package, run: + +```bash +pnpm turbo build --filter= +``` + +## Cleaning project + +To clean the project, run: + +```bash +pnpm clean +``` + +Then, reinstall the dependencies: + +```bash +pnpm install +``` + +## Formatting code + +To check for formatting errors using Prettier, run: + +```bash +pnpm format +``` + +To fix formatting errors using Prettier, run: + +```bash +pnpm format:fix +``` + +## Linting code + +To check for linting errors using ESLint, run: + +```bash +pnpm lint +``` + +To fix linting errors using ESLint, run: + +```bash +pnpm lint:fix +``` + +## Typechecking + +To typecheck the code using TypeScript for any type errors, run: + +```bash +pnpm typecheck +``` + +## Adding UI components + + + + To add a new web component, run: + + ```bash + pnpm --filter @turbostarter/ui-web ui:add + ``` + + This command will add and export a new component to `@turbostarter/ui-web` package. + + + + To add a new mobile component, run: + + ```bash + pnpm --filter @turbostarter/ui-mobile ui:add + ``` + + This command will add and export a new component to `@turbostarter/ui-mobile` package. + + + +## Services commands + + + To run the services containers locally, you need to have [Docker](https://www.docker.com/) installed on your machine. + + You can always use the cloud-hosted solution (e.g. [Neon](https://neon.tech/), [Turso](https://turso.tech/) for database) for your projects. + + +We have a few commands to help you manage the services containers (for local development). + +### Starting containers + +To start the services containers, run: + +```bash +pnpm services:start +``` + +It will run all the services containers. You can check their configs in `docker-compose.yml`. + +### Setting up services + +To setup all the services, run: + +```bash +pnpm services:setup +``` + +It will start all the services containers and run necessary setup steps. + +### Stopping containers + +To stop the services containers, run: + +```bash +pnpm services:stop +``` + +### Displaying status + +To check the status and logs of the services containers, run: + +```bash +pnpm services:status +``` + +### Displaying logs + +To display the logs of the services containers, run: + +```bash +pnpm services:logs +``` + +### Database commands + +We have a few commands to help you manage the database leveraging [Drizzle CLI](https://orm.drizzle.team/kit-docs/commands). + +#### Generating migrations + +To generate a new migration, run: + +```bash +pnpm with-env turbo db:generate +``` + +It will create a new migration `.sql` file in the `packages/db/migrations` folder. + +#### Running migrations + +To run the migrations against the db, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:migrate +``` + +It will apply all the pending migrations. + +#### Pushing changes directly + + + Make sure you know what you're doing before pushing changes directly to the db. + + +To push changes directly to the db, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:push +``` + +It lets you push your schema changes directly to the database and omit managing SQL migration files. + +#### Checking database status + +To check the status of the database, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:status +``` + +It will display the status of the applied migrations and the pending ones. + +```bash +Applied migrations: +- 0000_cooing_vargas +- 0001_curious_wallflower +- 0002_good_vertigo +- 0003_peaceful_devos +- 0004_fat_mad_thinker +- 0005_yummy_bucky +- 0006_glorious_vargas + +Pending migrations: +- 0007_nebulous_havok +``` + +#### Resetting database + +To reset the database, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:reset +``` + +It will reset the database to the initial state. + +#### Seeding database + +To seed the database with some example data (for development purposes), run: + +```bash +pnpm with-env turbo db:seed +``` + +It will populate your database with some example data. + +#### Checking database + +To check the database schema consistency, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:check +``` + +#### Studying database + +To study the database schema in the browser, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:studio +``` + +This will start the Studio on [https://local.drizzle.studio](https://local.drizzle.studio). + +## Tests commands + +### Running tests + +To run the tests, run: + +```bash +pnpm test +``` + +This will run all the tests in the project using Turbo tasks. As it leverages Turbo caching, it's [recommended](/docs/web/tests/unit#configuration) to run it in your CI/CD pipeline. + +### Running tests projects + +To run tests for all Vitest [Test Projects](https://vitest.dev/guide/projects), run: + +```bash +pnpm test:projects +``` + +This will run all the tests in the project using Vitest. + +### Watching tests + +To watch the tests, run: + +```bash +pnpm test:projects:watch +``` + +This will watch the tests for all [Test Projects](https://vitest.dev/guide/projects) and run them automatically when you make changes. + +### Generating code coverage + +To generate code coverage report, run: + +```bash +pnpm turbo test:coverage +``` + +This will generate a code coverage report in the `coverage` directory under `tooling/vitest` package. + +### Viewing code coverage + +To preview the code coverage report in the browser, run: + +```bash +pnpm turbo test:coverage:view +``` + +This will launch the report's `.html` file in your default browser. diff --git a/.context/turbostarter-framework-context/sections/mobile/installation/conventions.md b/.context/turbostarter-framework-context/sections/mobile/installation/conventions.md new file mode 100644 index 0000000..c17e76f --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/installation/conventions.md @@ -0,0 +1,86 @@ +--- +title: Conventions +description: Some standard conventions used across TurboStarter codebase. +url: /docs/mobile/installation/conventions +--- + +# Conventions + +You're not required to follow these conventions: they're simply a standard set of practices used in the core kit. If you like them - we encourage you to keep these during your usage of the kit - so to have consistent code style that you and your teammates understand. + +## Turborepo Packages + +In this project, we use [Turborepo packages](https://turbo.build/repo/docs/core-concepts/internal-packages) to define reusable code that can be shared across multiple applications. + +* **Apps** are used to define the main application, including routing, layout, and global styles. +* **Packages** shares reusable code add functionalities across multiple applications. They're configurable from the main application. + + + **Recommendation:** Do not create a package for your app code unless you plan to reuse it across multiple applications or are experienced in writing library code. + + If your application is not intended for reuse, keep all code in the app folder. This approach saves time and reduces complexity, both of which are beneficial for fast shipping. + + **Experienced developers:** If you have the experience, feel free to create packages as needed. + + +## Imports and Paths + +When importing modules from packages or apps, use the following conventions: + +* **From a package:** Use `@turbostarter/package-name` (e.g., `@turbostarter/ui`, `@turbostarter/api`, etc.). +* **From an app:** Use `~/` (e.g., `~/components`, `~/config`, etc.). + +## Enforcing conventions + +* [Prettier](https://prettier.io/) is used to enforce code formatting. +* [ESLint](https://eslint.org/) is used to enforce code quality and best practices. +* [TypeScript](https://www.typescriptlang.org/) is used to enforce type safety. + + + + + + + + + +## Code health + +TurboStarter provides a set of tools to ensure code health and quality in your project. + +### Github Actions + +By default, TurboStarter sets up Github Actions to run tests on every push to the repository. You can find the Github Actions configuration in the `.github/workflows` directory. + +The workflow has multiple stages: + +* `format` - runs Prettier to format the code. +* `lint` - runs ESLint to check for linting errors. +* `typecheck` - runs TypeScript to check for type errors. + +### Git hooks + +Together with TurboStarter we have set up a `commit-msg` hook which will check if your commit message follows the [conventional commit](https://www.conventionalcommits.org/en/v1.0.0/) message format. This is important for generating changelogs and keeping a clean commit history. + +Although we didn't ship any pre-commit hooks (we believe in shipping fast with moving checking code responsibility to CI), you can easily add them by using [Husky](https://typicode.github.io/husky/#/). + +#### Setting up the Pre-Commit Hook + +To do so, create a `pre-commit` file in the `./..husky` directory with the following content: + +```bash +#!/bin/sh + +pnpm typecheck +pnpm lint +``` + +Turborepo will execute the commands for all the affected packages - while skipping the ones that are not affected. + +#### Make the Pre-Commit Hook Executable + +```bash +chmod +x ./.husky/pre-commit +``` + +To test the pre-commit hook, try to commit a file with linting errors or type errors. The commit should fail, and you should see the error messages in the console. diff --git a/.context/turbostarter-framework-context/sections/mobile/installation/dependencies.md b/.context/turbostarter-framework-context/sections/mobile/installation/dependencies.md new file mode 100644 index 0000000..48414bc --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/installation/dependencies.md @@ -0,0 +1,73 @@ +--- +title: Managing dependencies +description: Learn how to manage dependencies in your project. +url: /docs/mobile/installation/dependencies +--- + +# Managing dependencies + +As the package manager we chose [pnpm](https://pnpm.io/). + + + It is a fast, disk space efficient package manager that uses hard links and symlinks to save one version of a module only ever once on a disk. It also has a great [monorepo support](https://pnpm.io/workspaces). Of course, you can change it to use [Bun](https://bunpkg.com), [yarn](https://yarnpkg.com) or [npm](https://www.npmjs.com) with minimal effort. + + +## Install dependency + +To install a package you need to decide whether you want to install it to the root of the monorepo or to a specific workspace. Installing it to the root makes it available to all packages, while installing it to a specific workspace makes it available only to that workspace. + +To install a package globally, run: + +```bash +pnpm add -w +``` + +To install a package to a specific workspace, run: + +```bash +pnpm add --filter +``` + +For example: + +```bash +pnpm add --filter @turbostarter/ui motion +``` + +It will install `motion` to the `@turbostarter/ui` workspace. + +## Remove dependency + +Removing a package is the same as installing but with the `remove` command. + +To remove a package globally, run: + +```bash +pnpm remove -w +``` + +To remove a package from a specific workspace, run: + +```bash +pnpm remove --filter +``` + +## Update a package + +Updating is a bit easier since there is a nice way to update a package in all workspaces at once: + +```bash +pnpm update -r +``` + + + When you update a package, pnpm will respect the [semantic versioning](https://docs.npmjs.com/about-semantic-versioning) rules defined in the `package.json` file. If you want to update a package to the latest version, you can use the `--latest` flag. + + +## Renovate bot + +By default, TurboStarter comes with [Renovate](https://www.npmjs.com/package/renovate) enabled. It is a tool that helps you manage your dependencies by automatically creating pull requests to update your dependencies to the latest versions. You can find its configuration in the `.github/renovate.json` file. Learn more about it in the [official docs](https://docs.renovatebot.com/configuration-options/). + +When it creates a pull request, it is treated as a normal PR, so all tests and preview deployments will run. **It is recommended to always preview and test the changes in the staging environment before merging the PR to the main branch to avoid breaking the application.** + + diff --git a/.context/turbostarter-framework-context/sections/mobile/installation/development.md b/.context/turbostarter-framework-context/sections/mobile/installation/development.md new file mode 100644 index 0000000..065bbde --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/installation/development.md @@ -0,0 +1,110 @@ +--- +title: Development +description: Get started with the code and develop your mobile SaaS. +url: /docs/mobile/installation/development +--- + +# Development + +## Prerequisites + +To get started with TurboStarter, ensure you have the following installed and set up: + +* [Node.js](https://nodejs.org/en) (22.x or higher) +* [Docker](https://www.docker.com) (only if you want to use local services e.g. database) +* [pnpm](https://pnpm.io) +* [Firebase](https://firebase.google.com) project (optional for some features - check [Firebase project](/docs/mobile/installation/firebase) section for more details) + +## Project development + + + + ### Set up environment + + We won't copy the official docs, as there is quite a bit of setup you need to make to get started with iOS and Android development and it also depends what approach you want to take. + + [Check this official setup guide to get started](https://docs.expo.dev/get-started/set-up-your-environment/). After you're done with the setup, go back to this guide and continue with the next step. + + You can pick if you want to develop the app for iOS or Android by using the real device or the simulator. + + + We recommend using the simulators and [development builds](https://docs.expo.dev/develop/development-builds/create-a-build/) for development, as it is more real and reliable approach. It also won't limit you in terms of native dependencies (required for e.g. [analytics](/docs/mobile/analytics/overview)). + + Of course, you can start with the simplest approach (using [Expo Go](https://expo.dev/go)) and when you iterate further, switch to different approach. + + + + + ### Install dependencies + + Install the project dependencies by running the following command: + + ```bash + pnpm i + ``` + + + It is a fast, disk space efficient package manager that uses hard links and symlinks to save one version of a module only ever once on a disk. It also has a great [monorepo support](https://pnpm.io/workspaces). Of course, you can change it to use [Bun](https://bunpkg.com), [yarn](https://yarnpkg.com) or [npm](https://www.npmjs.com) with minimal effort. + + + + + ### Setup environment variables + + Create a `.env.local` files from `.env.example` files and fill in the required environment variables. + + You can use the following command to recursively copy the `.env.example` files to the `.env.local` files: + + + + ```bash + find . -name ".env.example" -exec sh -c 'cp "$1" "${1%.example}.local"' _ {} \; + ``` + + + + ```bash + Get-ChildItem -Recurse -Filter ".env.example" | ForEach-Object { + Copy-Item $_.FullName ($\_.FullName -replace '\.example$', '.local') + } + ``` + + + + Check [Environment variables](/docs/web/configuration/environment-variables) for more details on setting up environment variables. + + + + ### Setup services + + If you want to use local services like database etc. (**recommended for development purposes**), ensure Docker is running, then setup them with: + + ```bash + pnpm services:setup + ``` + + This command initiates the containers and runs necessary setup steps, ensuring your services are up to date and ready to use. + + + + ### Start development server + + To start the application development server, run: + + ```bash + pnpm dev + ``` + + Your development server should now be running at `http://localhost:8081`. + + ![Metro server](/images/docs/mobile/metro-server.png) + + Scan the QR code with your mobile device to start the app or press the appropriate key on your keyboard to run it on simulator. In case of any issues check the [Troubleshooting](https://docs.expo.dev/troubleshooting/overview/) section. + + + + ### Publish to stores + + When you're ready to publish the project to the stores, follow [guidelines](/docs/mobile/marketing) and [checklist](/docs/mobile/publishing/checklist) to ensure everything is set up correctly. + + diff --git a/.context/turbostarter-framework-context/sections/mobile/installation/editor-setup.md b/.context/turbostarter-framework-context/sections/mobile/installation/editor-setup.md new file mode 100644 index 0000000..82fdb2c --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/installation/editor-setup.md @@ -0,0 +1,69 @@ +--- +title: Editor setup +description: Learn how to set up your editor for the fastest development experience. +url: /docs/mobile/installation/editor-setup +--- + +# Editor setup + +Of course you can use every IDE you like, but you will have the best possible developer experience with this starter kit when using **VSCode-based** editor with the suggested settings and extensions. + +## Settings + +We have included most recommended settings in the `.vscode/settings.json` file to make your development experience as smooth as possible. It include mostly configs for tools like Prettier, ESLint and Tailwind which are used to enforce some conventions across the codebase. You can adjust them to your needs. + +## LLM rules + +We exposed a special endpoint that will scan all the docs and return the content as a text file which you can use to train your LLM or put in a prompt. You can find it at [/llms.txt](/llms.txt). + +The repository also includes a custom rules for most popular AI editors and agents to ensure that AI completions are working as expected and following our conventions. + +### AGENTS.md + +We've integrated specific rules that help maintain code quality and ensure AI-assisted completions align with our project standards. + +You can find them in the `AGENTS.md` file at the root of the project. This format is a standardized way to instruct AI agents to follow project conventions when generating code and it's used by over **20,000** open-source projects - including [Cursor](https://cursor.sh/), [Aider](https://aider.chat/), [Codex from OpenAI](https://openai.com/blog/openai-codex/), [Jules from Google](https://jules.ai/), [Windsurf](https://windsurf.dev/), and many others. + +```md title="AGENTS.md" +### Code Style and Structure + +- Write concise, technical TypeScript code with accurate examples +- Use functional and declarative programming patterns; avoid classes +- Prefer iteration and modularization over code duplication + +### Naming Conventions + +.... +``` + +To learn more about `AGENTS.md` rules check out the [official documentation](https://agents.md/). + +## Extensions + +Once you cloned the project and opened it in VSCode you should be promted to install suggested extensions which are defined in the `.vscode/extensions.json` automatically. In case you rather want to install them manually you can do so at any time later. + +These are the extensions we recommend: + +### ESLint + +Global extension for static code analysis. It will help you to find and fix problems in your JavaScript code. + + + +### Prettier + +Global extension for code formatting. It will help you to keep your code clean and consistent. + + + +### Pretty TypeScript Errors + +Improves TypeScript error messages shown in the editor. + + + +### Tailwind CSS IntelliSense + +Adds IntelliSense for Tailwind CSS classes to enable autocompletion and linting. + + diff --git a/.context/turbostarter-framework-context/sections/mobile/installation/firebase.md b/.context/turbostarter-framework-context/sections/mobile/installation/firebase.md new file mode 100644 index 0000000..87d8414 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/installation/firebase.md @@ -0,0 +1,102 @@ +--- +title: Firebase project +description: Learn how to set up a Firebase project for your TurboStarter mobile app. +url: /docs/mobile/installation/firebase +--- + +# Firebase project + +For some features of your mobile app, you will need to set up a Firebase project. It's a requirement enforced by how these features are implemented under the hood and we cannot change it. + +You would need a Firebase project to use the following features: + +* [Analytics](/docs/mobile/analytics/overview) with [Google Analytics](/docs/mobile/analytics/configuration#google-analytics) provider + +Here, we'll go through the steps to set up a Firebase project and link it to your mobile app. + + + In development environment, the integration with Firebase is possible only when using a [development build](https://docs.expo.dev/workflow/overview/#development-builds). It means that **it won't work in the [Expo Go](https://expo.dev/go) app**. + + + + + ## Create a Firebase project + + First things first, you need to create a Firebase project. You can do this by going to the [Firebase console](https://console.firebase.google.com/) and clicking on "Add Project": + + ![Create a Firebase project](/images/docs/mobile/installation/firebase/create-project.png) + + Name it as you want, and proceed to the dashboard. + + + + ## Install Firebase SDK + + To install React Native Firebase's base app module, run the following command in your mobile app directory: + + ```bash + npx expo install @react-native-firebase/app + ``` + + + + ## Configure Firebase modules + + The recommended approach to configure React Native Firebase is to use [Expo Config Plugins](https://docs.expo.dev/config-plugins/introduction/). + + To enable Firebase on the native Android and iOS platforms, create and download Service Account files for each platform from your Firebase project. + + You can find them in the dashboard under the Firebase project settings: + + ![Download Service Account files](/images/docs/mobile/installation/firebase/config-files.png) + + For Android, it will be a `google-services.json` file, and for iOS it will be a `GoogleService-Info.plist` file. + + Then provide paths to the downloaded files in the following `app.config.ts` fields: [`android.googleServicesFile`](https://docs.expo.io/versions/latest/config/app/#googleservicesfile-1) and [`ios.googleServicesFile`](https://docs.expo.io/versions/latest/config/app/#googleservicesfile). This is how an example configuration looks like: + + ```ts title="app.config.ts" + export default ({ config }: ConfigContext): ExpoConfig => ({ + ...config, + ios: { + googleServicesFile: "./GoogleService-Info.plist", + }, + android: { + googleServicesFile: "./google-services.json", + }, + plugins: [ + "@react-native-firebase/app", + [ + "expo-build-properties", + { + ios: { + useFrameworks: "static", + }, + }, + ], + ], + }); + ``` + + + For iOS only, since `firebase-ios-sdk` requires `use_frameworks` you need to configure `expo-build-properties` by adding `"useFrameworks": "static"`. + + + Listing a module in the Config Plugins (the `plugins` array in the config above) is only required for React Native Firebase modules that involve native installation steps - e.g. modifying the Xcode project, `Podfile`, `build.gradle`, `AndroidManifest.xml` etc. React Native Firebase modules without native steps will work out of the box. + + + + ## Generate native code + + If you are compiling your app locally, you'll need to regenerate the native code for the platforms to pick up the changes: + + ```bash + npx expo prebuild --clean + ``` + + Then, you could follow the same steps as in the [development environment setup](/docs/mobile/installation/development) guide to run the app locally or [build a production version](/docs/mobile/publishing/checklist#build-your-app) of your app. + + + +Et voilà! You've set up and linked your Firebase project to your mobile app 🎉 + +You can learn more about the Firebase integration and it's possibilities in the [official documentation](https://rnfirebase.io/). diff --git a/.context/turbostarter-framework-context/sections/mobile/installation/structure.md b/.context/turbostarter-framework-context/sections/mobile/installation/structure.md new file mode 100644 index 0000000..a083478 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/installation/structure.md @@ -0,0 +1,120 @@ +--- +title: Project structure +description: Learn about the project structure and how to navigate it. +url: /docs/mobile/installation/structure +--- + +# Project structure + +The main directories in the project are: + +* `apps` - the location of the main apps +* `packages` - the location of the shared code and the API + +### `apps` Directory + +This is where the apps live. It includes web app (Next.js), mobile app (React Native - Expo), and the browser extension (WXT - Vite + React). Each app has its own directory. + +### `packages` Directory + +This is where the shared code and the API for packages live. It includes the following: + +* shared libraries (database, mailers, cms, billing, etc.) +* shared features (auth, mails, billing, ai etc.) +* UI components (buttons, forms, modals, etc.) + +All apps can use and reuse the API exported from the packages directory. This makes it easy to have one, or many apps in the same codebase, sharing the same code. + +## Repository structure + +By default the monorepo contains the following apps and packages: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +## Mobile application structure + +The mobile application is located in the `apps/mobile` folder. It contains the following folders: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.context/turbostarter-framework-context/sections/mobile/installation/update.md b/.context/turbostarter-framework-context/sections/mobile/installation/update.md new file mode 100644 index 0000000..df473b9 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/installation/update.md @@ -0,0 +1,97 @@ +--- +title: Updating codebase +description: Learn how to update your codebase to the latest version. +url: /docs/mobile/installation/update +--- + +# Updating codebase + +If you've been following along with our previous guides, you should already have a Git repository set up for your project, with an `upstream` remote pointing to the original repository. + +Updating your project involves fetching the latest changes from the `upstream` remote and merging them into your project. Let's dive into the steps! + + + + ## Stash changes + + + If you don't have any changes to stash, you can skip this step and proceed with the update process. + + Alternatively, you can [commit](https://git-scm.com/docs/git-commit) your changes. + + + If you have any uncommitted changes, stash them before proceeding. It will allow you to avoid any conflicts that may arise during the update process. + + ```bash + git stash + ``` + + This command will save your changes in a temporary location, allowing you to retrieve them later. Once you're done updating, you can apply the stash to your working directory. + + ```bash + git stash apply + ``` + + + + ## Pull changes + + Pull the latest changes from the `upstream` remote. + + ```bash + git pull upstream main + ``` + + When prompted the first time, please opt for merging instead of rebasing. + + Don't forget to run `pnpm i` in case there are any updates in the dependencies. + + + + ## Resolve conflicts + + If there are any conflicts during the merge, Git will notify you. You can resolve them by opening the conflicting files in your code editor and making the necessary changes. + + + If you find conflicts in the `pnpm-lock.yaml file`, accept either of the two changes (avoid manual edits), then run: + + ```bash + pnpm i + ``` + + Your lock file will now reflect both your changes and the updates from the upstream repository. + + + + + ## Run a health check + + After resolving the conflicts, it's time to test your project to ensure everything is working as expected. Run your project locally and navigate through the various features to verify that everything is functioning correctly. + + For a quick health check, you can run: + + ```bash + pnpm lint + pnpm typecheck + + ``` + + If everything looks good, you're all set! Your project is now up to date with the latest changes from the `upstream` repository. + + + + ## Commit and push + + Once everything is working fine, don't forget to commit your changes using: + + ```bash + git commit -m "" + ``` + + and push them to your remote repository with: + + ```bash + git push origin + ``` + + diff --git a/.context/turbostarter-framework-context/sections/mobile/internationalization.md b/.context/turbostarter-framework-context/sections/mobile/internationalization.md new file mode 100644 index 0000000..8d384b9 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/internationalization.md @@ -0,0 +1,127 @@ +--- +title: Internationalization +description: Learn how to internationalize your mobile app. +url: /docs/mobile/internationalization +--- + +# Internationalization + +TurboStarter mobile uses [i18next](https://www.i18next.com/) and [expo-localization](https://docs.expo.dev/versions/latest/sdk/localization/) for internationalization. This powerful combination allows you to leverage both i18next's mature translation framework and Expo's native device locale detection. + + + While i18next handles the translation management, expo-localization provides + seamless integration with the device's locale settings. This means your app + can automatically detect and adapt to the user's preferred language, while + still maintaining the flexibility to override it when needed. + + +The mobile app's internationalization is configured to work out of the box with: + +* Automatic device language detection +* Right-to-left (RTL) layout support +* Locale-aware date and number formatting +* Fallback language handling + +You can read more about the underlying technologies in their documentation: + +* [i18next documentation](https://www.i18next.com/overview/getting-started) +* [expo-localization documentation](https://docs.expo.dev/versions/latest/sdk/localization/) + +![i18next logo](/images/docs/i18next.jpg) + +## Configuration + +The global configuration is defined in the `@turbostarter/i18n` package and shared across all applications. You can read more about it in the [web configuration](/docs/web/internationalization/configuration) documentation. + +By default, the locale is automatically detected based on the user's device settings. You can override it and set the default locale of your mobile app in the [app configuration](/docs/mobile/configuration/app) file. + +## Translating app + +To translate individual components and screens, you can use the `useTranslation` hook. + +```tsx +import { useTranslation } from "@turbostarter/i18n"; + +export default function MyComponent() { + const { t } = useTranslation(); + + return {t("hello")}; +} +``` + +It's a recommended way to translate your app. + +### Store presence + +If you plan on shipping your app to different countries or regions or want it to support various languages, you can provide localized strings for things like the display name and system dialogs. + +To do so, check the [official Expo documentation](https://docs.expo.dev/guides/localization/) as it requires modifying your app configuration (`app.config.ts`). + +You can find the resources below helpful in this process: + + + + + + + + + +## Language switcher + +TurboStarter ships with a language customizer component that allows you to switch between languages. You can import and use the `LocaleCustomizer` component and drop it anywhere in your application to allow users to change the language seamlessly. + +```tsx +import { LocaleCustomizer } from "@turbostarter/ui-mobile/i18n"; + +export default function MyComponent() { + return ; +} +``` + +The component automatically displays all languages configured in your i18n settings. When a user switches languages, it will be reflected in the app and saved into persistent storage to keep the language across app restarts. + +## Best practices + +Here are key best practices for managing translations in your mobile app: + +* Use clear, hierarchical translation keys for easy maintenance + + ```ts + // ✅ Good + "screen.home.welcome"; + "component.button.submit"; + + // ❌ Bad + "welcomeText"; + ``` + +* Organize translations by app screens and features + + ``` + translations/ + ├── en/ + │ ├── layout.json + │ └── common.json + └── es/ + ├── layout.json + └── common.json + ``` + +* Consider device language settings and regional formats + +* Cache translations locally for offline access + +* Handle dynamic content for mobile contexts: + + ```ts + // Device-specific messages + t("errors.noConnection"); // "Check your internet connection" + + // Dynamic values + t("storage.space", { gb: 2.5 }); // "2.5 GB available" + ``` + +* Keep translations concise - mobile screens have limited space + +* Test translations with different screen sizes and orientations diff --git a/.context/turbostarter-framework-context/sections/mobile/marketing.md b/.context/turbostarter-framework-context/sections/mobile/marketing.md new file mode 100644 index 0000000..213ff5b --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/marketing.md @@ -0,0 +1,113 @@ +--- +title: Marketing +description: Learn how to market your mobile application. +url: /docs/mobile/marketing +--- + +# Marketing + +As you saw in the [Extras](/docs/mobile/extras) section, TurboStarter comes with a lot of tips and tricks to make your product better and help you launch your app faster with higher traffic. + +The same applies to [submission tips](/docs/mobile/extras#submission-tips) to help you get your app approved by Apple and Google faster. + +We'll talk more about the whole process of deploying and publishing your app in the [Publishing](/docs/mobile/publishing/checklist) section, here we'll go through some guidelines that you need to follow to make your store's visibility higher. + +## Before you submit + +To help your app approval go as smoothly as possible, review the common missteps listed below that can slow down the review process or trigger a rejection. This doesn't replace the official guidelines or guarantee approval, but making sure you can check every item on the list is a good start. + +Make sure you: + +* Test your app for crashes and bugs +* Ensure that all app information and metadata is complete and accurate +* Update your contact information in case App Review needs to reach you +* Provide App Review with full access to your app. If your app includes account-based features, provide either an active demo account or fully-featured demo mode, plus any other hardware or resources that might be needed to review your app (e.g. login credentials or a sample QR code) +* Enable backend services so that they're live and accessible during review +* Include detailed explanations of non-obvious features and in-app purchases in the App Review notes, including supporting documentation where appropriate + +Following these basic steps during development and before submission will help you get your app approved faster. + +## App Store (iOS) + +Apple reviews are much stricter than Google reviews, so you need to make sure your app is ready for the App Store. + +### Guidelines + +Apple has a set of [guidelines](https://developer.apple.com/app-store/review/guidelines/) that you need to follow to make sure your app can be accepted in the App Store. + +These include: + +* **Safety**: Your app must not contain content or behavior that is harmful, abusive, or threatening. +* **Performance**: Your app must be performant and stable, with a smooth user experience. +* **Business**: Your app must not engage in unethical or deceptive practices. +* **Design**: Your app must have a clean and intuitive design. +* **Legal**: Your app must comply with all relevant laws and regulations. + +You can read more about each guideline in the [official App Review Guidelines](https://developer.apple.com/app-store/review/guidelines/). + +### Search optimization + +App store optimization is the process of increasing an app or game's visibility in an app store, with the objective of increasing organic app downloads. Apps are more visible when they rank high on a wide variety of search terms, maintain a high position in the top charts, or get featured on the store. + +There are a few actions that you can take to improve your app's visibility in the App Store: + +* **Choose accurate keywords**: Use relevant keywords in your app's store listing. +* **Create a compelling app name, subtitle, and description**: Your app's title should be catchy and descriptive, the same applies to the subtitle and description. +* **Assign the right categories**: Make sure your app is categorized in the right category, this will help you reach the right audience. +* **Foster positive ratings**: Ratings and reviews appear on your product page and influence how your app ranks in search results. They can encourage people to engage with your app, so focus on providing a great app experience that motivates users to leave positive reviews. +* **Publish in-app events**: You can publish in-app events to promote your app and encourage users to engage with your app. (e.g. game competitions) +* **Promote in-app purchases**: Your promoted in-app purchases appear in search results on the App Store. Tapping an in-app purchase leads to your product page, which displays your app's description, screenshots, app previews, and in-app events — and lets people initiate an in-app purchase. + +Read more about App Store Optimization in the [official documentation](https://developer.apple.com/app-store/search/). + + + + + + + +## Google Play (Android) + +Google reviews are less stringent than Apple reviews and usually take less time to review, but you still need to make sure your app is ready for the Play Store. + +### Guidelines + +Google has its own guidelines that apps must adhere to. Some important aspects to consider include: + +* **Spam, functionality, and user experience**: Your app must not be spammy, must work as expected and must provide a good user experience. +* **Restricted content**: Before submitting an app to Google Play, ensure it complies with these content policies and with local laws. +* **Privacy**: Apps that are deceptive, malicious, or intended to abuse or misuse any network, device, or personal data are strictly prohibited +* **Monetization**: Your app must not engage in unethical or deceptive practices. + +For more detailed information and an interactive checklist, check the [Google requirements page](https://developers.google.com/workspace/marketplace/about-app-review). + +### Search optimization + +Ensuring that your app and store listing is thorough and optimized is an important factor in getting discovered by users on Google Play. + +Follow these steps to optimize your app's visibility on Google Play: + +* **Build a comprehensive store listing**: This includes providing accurate **title**, **description** and **promo text**. +* **Use high-quality graphics and images**: App icons, images, and screenshots help make your app stand out in search results, categories, and featured app lists. +* **Diversify your audience**: Google provides automated machine translations of store listings that you don't explicitly define for your app. However, using a professional translation service for your *Description* can lead to better search results and discoverability for worldwide users. +* **Create a great user experience**: Google Play search factors in the overall experience of your app based on user behavior and feedback. Apps are ranked based on a combination of ratings, reviews, downloads, and other factors. + + + + + + + +## Common mistakes + +There are a few common mistakes that you should avoid to make sure your app can be accepted in the stores. Apple reports that, on average, over **40%** of unresolved issues relate to [guideline 2.1: App Completeness](https://developer.apple.com/app-store/review/guidelines/#2.1), so make sure to avoid these: + +* **Crashes and bugs** +* **Broken links** +* **Placeholder content** +* **Incomplete information** +* **Privacy policy issues** +* **Inaccurate screenshots** +* **Repeated submission of similar apps** + +Don't worry if your first submission is rejected, improve it, fix all the mentioned issues and try again. diff --git a/.context/turbostarter-framework-context/sections/mobile/monitoring/overview.md b/.context/turbostarter-framework-context/sections/mobile/monitoring/overview.md new file mode 100644 index 0000000..4dbf257 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/monitoring/overview.md @@ -0,0 +1,113 @@ +--- +title: Overview +description: Get started with mobile monitoring in TurboStarter. +url: /docs/mobile/monitoring/overview +--- + +# Overview + +TurboStarter ships with powerful, provider-agnostic monitoring helpers for the mobile app so you can answer the questions that matter in production: **what broke**, **on which screen**, and **which users were impacted**. It's designed for simplicity and extensibility, and works with multiple providers behind a single API. + +## Capturing exceptions + +On mobile, you'll usually want to report errors from a few key places: + +* **UI/runtime crashes**: unexpected JS errors that would otherwise blank the screen or break navigation. +* **Async work**: background tasks, effects, and data fetching where failures are easy to miss. +* **Manual reporting**: wrap critical flows (auth, purchases, sync, deep-links) with `try/catch` so you can attach context when things go wrong. + +```tsx +import { Pressable, Text } from "react-native"; +import { captureException } from "@turbostarter/monitoring-mobile"; + +export default function ExampleComponent() { + const handleClick = () => { + try { + /* some risky operation */ + } catch (error) { + captureException(error); + } + }; + + return ( + + Trigger Exception + + ); +} +``` + + + `try/catch` (and most JS error handlers) can only see JavaScript exceptions. Native crashes (for example, a hard crash in a native module) typically require provider-specific native setup to capture crash reports. Use the provider pages below for platform details. + + +## Identifying users + +Error reports become much more actionable once they're tied to a signed-in user. TurboStarter supports identifying the current user after the auth session resolves, so your monitoring provider can associate errors with a stable user profile (without you plumbing this through every capture call). + +If you want richer filtering, pass non-sensitive traits (plan, role, locale) depending on what your provider supports. + +```tsx title="monitoring.tsx" +import { useEffect } from "react"; +import { identify } from "@turbostarter/monitoring-mobile"; +import { authClient } from "~/lib/auth"; + +export const MonitoringProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const session = authClient.useSession(); + + useEffect(() => { + if (session.isPending) { + return; + } + + identify(session.data?.user ?? null); + }, [session]); + + return children; +}; +``` + + + Identify users with **stable IDs** and only the traits you need for debugging. Avoid sending PII or secrets (tokens, raw emails, payment details) unless you've explicitly decided it's acceptable for your monitoring provider and compliance requirements. + + +## Providers + +TurboStarter can report through different monitoring providers while keeping your app code consistent. Choose a provider (or swap later) by updating the exports/config in the monitoring package. + + + + + + + +## Recommended practices + + + + Prioritize crashes, failed network calls that break a flow, and unexpected + states. Skip noisy “expected” errors (validation, user cancellations). + + + + Include the screen/route, the action the user took, and relevant IDs + (request id, order id). Mobile issues are often device- or version-specific, + so make sure app version/build info is included by your provider. + + + + If an effect or retry path can fire repeatedly, debounce or dedupe your + capture calls so you don't spam reports (or exceed quotas). + + + + Keep environments isolated so test devices don't pollute production signal. + Tag builds/releases so you can correlate spikes with deployments. + + + +With solid capture + identification in place, mobile monitoring becomes a feedback loop: you can spot regressions quickly, understand who they affect, and validate fixes by release. diff --git a/.context/turbostarter-framework-context/sections/mobile/monitoring/posthog.md b/.context/turbostarter-framework-context/sections/mobile/monitoring/posthog.md new file mode 100644 index 0000000..51eca55 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/monitoring/posthog.md @@ -0,0 +1,155 @@ +--- +title: PostHog +description: Learn how to setup PostHog as your mobile monitoring provider. +url: /docs/mobile/monitoring/posthog +--- + +# PostHog + +[PostHog](https://posthog.com/) is a product analytics platform that can also help with monitoring via error tracking and session replay. On mobile, it's especially useful when you want to connect **what went wrong** with **what the user did** right before it happened. + +TurboStarter keeps monitoring provider selection behind a unified API, so you can route captures to PostHog without changing your app code. + + + You'll need a PostHog account ([cloud](https://app.posthog.com/signup) or [self-hosted](https://posthog.com/docs/self-host)) to use it as your monitoring provider. + + + + PostHog is one of the preconfigured analytics providers for mobile apps. If you want product analytics (events, screens, funnels), see [analytics overview](/docs/mobile/analytics/overview) and the [PostHog configuration](/docs/mobile/analytics/configuration#posthog). + + +![Posthog banner](/images/docs/web/monitoring/posthog/banner.jpg) + +## Configuration + +PostHog makes it easy to monitor your mobile app for errors and issues, giving you full visibility into when things go wrong. With TurboStarter, you can enable PostHog-based monitoring in just a few steps, sending errors and related user actions to your PostHog dashboard for debugging and product improvement. + + + + ### Create a project + + Create a new PostHog [project](https://app.posthog.com/project/settings) for your mobile app. You can do this from the [PostHog dashboard](https://app.posthog.com) using the *New Project* action. + + + + ### Activate PostHog as your monitoring provider + + TurboStarter chooses the mobile monitoring provider through exports in `packages/monitoring/mobile`. To route monitoring events to PostHog, export the PostHog implementation from the package entrypoint: + + ```ts title="index.ts" + // [!code word:posthog] + export * from "./posthog"; + export * from "./posthog/env"; + ``` + + + + ### Set environment variables + + Add your PostHog project key (and host, if you're not using the default cloud region) to your mobile app env. Set these locally and in your build environment (for example, in your [EAS build profile](/docs/mobile/publishing/checklist#environment-variables)): + + ```dotenv title="apps/mobile/.env.local" + EXPO_PUBLIC_POSTHOG_KEY="your-posthog-project-api-key" + EXPO_PUBLIC_POSTHOG_HOST="https://us.i.posthog.com" + ``` + + + +That's it - launch the app, trigger an error, and confirm events are arriving in your PostHog project. + +![Posthog error](/images/docs/web/monitoring/posthog/error.png) + +If you want to go beyond basic capture (session replay, feature flags, richer device/session context), follow [PostHog's React Native setup guidance](https://posthog.com/docs/error-tracking/installation/react-native). + + + + + + + +## Uploading source maps + +**Source maps** map the bundled/minified JavaScript running on devices back to your original source code. Without them, mobile stack traces are often hard to read and difficult to action. + + + With source maps uploaded to PostHog, error reports can be symbolicated so stack traces point to the real files and line numbers from your project. + + +PostHog's React Native source maps flow has two main parts: + +* **Inject debug IDs** into the bundle during bundling (Metro) +* **Upload source maps** during your iOS/Android build (or via CLI in CI) + + + + ### Install and authenticate the PostHog CLI + + Install the CLI globally: + + ```bash + npm install -g @posthog/cli + ``` + + Then authenticate: + + ```bash + posthog-cli login + ``` + + If you're running in CI, you can authenticate with environment variables instead: + + ```dotenv + POSTHOG_CLI_HOST="https://us.posthog.com" + POSTHOG_CLI_ENV_ID="your-posthog-project-id" + POSTHOG_CLI_TOKEN="your-personal-api-key" + ``` + + + + ### Inject debug IDs with Metro + + Automatic injection relies on Expo's debug ID support. Update `metro.config.js` to use PostHog's Expo config: + + ```js title="metro.config.js" + const { getPostHogExpoConfig } = require("posthog-react-native/metro"); + + const config = getPostHogExpoConfig(__dirname); + + module.exports = config; + ``` + + + + ### Upload source maps during builds + + If you can use the Expo plugin (recommended for managed EAS builds), add the plugin to your Expo config: + + ```ts title="app.config.ts" + export default ({ config }: ConfigContext): ExpoConfig => ({ + ...config, + plugins: ["posthog-react-native/expo"], + }); + ``` + + If you can't use the Expo plugin, PostHog also supports wiring uploads directly into: + + * **Android**: your Gradle build (`android/app/build.gradle`) + * **iOS**: your Xcode “Bundle React Native code and images” build phase + + Follow the [official PostHog instructions](https://posthog.com/docs/error-tracking/upload-source-maps/react-native) for the exact snippets for each platform. + + + + ### Verify uploads in PostHog + + After a release build, confirm your symbol sets are present in [PostHog project error tracking dashboard](https://app.posthog.com/settings/project-error-tracking#error-tracking-symbol-sets) and then trigger a test error to ensure stack traces are resolving as expected. + + + +With debug IDs injected and source maps uploaded, PostHog can symbolicate React Native errors so stack traces point back to your original source files. If traces still look minified, double-check that you're testing a release build and that the latest symbol sets are present in your project settings. + + + + + + diff --git a/.context/turbostarter-framework-context/sections/mobile/monitoring/sentry.md b/.context/turbostarter-framework-context/sections/mobile/monitoring/sentry.md new file mode 100644 index 0000000..8142a0d --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/monitoring/sentry.md @@ -0,0 +1,147 @@ +--- +title: Sentry +description: Learn how to setup Sentry as your mobile monitoring provider. +url: /docs/mobile/monitoring/sentry +--- + +# Sentry + +[Sentry](https://sentry.io/) is a popular error monitoring platform that captures crashes and exceptions from production devices and helps you debug them with stack traces, breadcrumbs, and user context. + +TurboStarter's mobile monitoring layer is provider-agnostic, but Sentry is a great default when you want reliable crash reporting plus readable stack traces in release builds. + + + To use Sentry, create an [account in Sentry](https://sentry.io/signup) first. + + +![Sentry banner](/images/docs/web/monitoring/sentry/banner.png) + +## Configuration + +TurboStarter integrates effortlessly with Sentry, so you can capture application errors and analyze performance from development through production. Setting up Sentry as your provider lets you quickly find and fix issues, contributing to a more robust and dependable app. + +Follow the steps below to integrate Sentry with your TurboStarter project. + + + + ### Create a project + + Begin by creating a [project](https://docs.sentry.io/product/projects/) in Sentry. You can set this up from your [dashboard](https://sentry.io/settings/account/projects/) by clicking the *Create Project* button. + + + + ### Activate Sentry as your monitoring provider + + The monitoring provider to use is determined by the exports in `packages/monitoring/mobile` package. To activate Sentry as your monitoring provider, you need to update the exports in: + + ```ts title="index.ts" + // [!code word:sentry] + export * from "./sentry"; + export * from "./sentry/env"; + ``` + + If you want to customize the provider, you can find its definition in `packages/monitoring/mobile/src/providers/sentry` directory. + + + + ### Set environment variables + + Based on your [project settings](https://sentry.io/project/settings), fill the following environment variables in your `.env.local` file in `apps/mobile` directory and your deployment environment (e.g. [EAS build profile](/docs/mobile/publishing/checklist#environment-variables)): + + ```dotenv title="apps/mobile/.env.local" + EXPO_PUBLIC_SENTRY_DSN="your-sentry-dsn" + EXPO_PUBLIC_PROJECT_ENVIRONMENT="your-project-environment" + ``` + + + + ### Wrap your app + + Install the Sentry React Native SDK in the `mobile` workspace. + + ```bash + pnpm i @sentry/react-native --filter mobile + ``` + + And then wrap the root component of your application with Sentry.wrap: + + ```tsx title="app/_layout.tsx" + import * as Sentry from "@sentry/react-native"; + + export default Sentry.wrap(RootLayout); + ``` + + + TurboStarter initializes the SDK for you based on env + provider exports; you only need to wrap the root component. + + + + +You're all set! Start your app and view any errors or exceptions directly in your [Sentry dashboard](https://sentry.io/settings/account/projects/). + +![Sentry error](/images/docs/web/monitoring/sentry/error.jpg) + +You can tailor the setup further if needed. For more details, refer to the [official Sentry documentation](https://docs.sentry.io/platforms/react-native/features/). + + + + + + + +## Uploading source maps + +Readable stack traces in Sentry require uploading source maps for release builds. For Expo projects, Sentry recommends enabling **two pieces**: + +* the **Sentry Expo config plugin** (uploads during native builds) +* the **Sentry Metro plugin** (adds debug IDs so bundles and source maps match) + +### Add the Sentry Expo plugin + +Add `@sentry/react-native/expo` plugin to your Expo config (`app.config.ts`): + +```ts title="app.config.ts" +export default ({ config }: ConfigContext): ExpoConfig => ({ + ...config, + plugins: [ + [ + "@sentry/react-native/expo", + { + url: "https://sentry.io/", + project: "your-sentry-project", + organization: "your-sentry-organization", + }, + ], + ], +}); +``` + +Then provide an auth token through environment variables (locally in `.env.local` file in `apps/mobile` directory) and your build environment: + +```dotenv title="apps/mobile/.env.local" +SENTRY_AUTH_TOKEN="your-sentry-auth-token" +``` + +### Add the Sentry Metro plugin + +To ensure unique Debug IDs are assigned to the generated bundles and source maps, add the Sentry Metro Plugin to the configuration. + +Update `metro.config.js` to use `getSentryExpoConfig`: + +```js title="metro.config.js" +const { getSentryExpoConfig } = require("@sentry/react-native/metro"); + +const config = getSentryExpoConfig(__dirname); + +module.exports = config; +``` + +With the Expo plugin + Metro plugin in place, source maps are uploaded automatically during release native builds and EAS builds (debug builds typically rely on Metro's symbolication). + +Take a moment to test your setup by triggering an error in your app, then confirm that source maps are resolving stack traces accurately in your [Sentry dashboard](https://sentry.io/settings/account/projects/). For advanced setup details, troubleshooting, or further customization with React Native and Expo, refer to the [official Sentry documentation](https://docs.sentry.io/platforms/react-native/guides/expo/sourcemaps/). + + + + + + diff --git a/.context/turbostarter-framework-context/sections/mobile/organizations/active-organization.md b/.context/turbostarter-framework-context/sections/mobile/organizations/active-organization.md new file mode 100644 index 0000000..1f5c588 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/organizations/active-organization.md @@ -0,0 +1,77 @@ +--- +title: Active organization +description: Set and switch the current organization context within your application. +url: /docs/mobile/organizations/active-organization +--- + +# Active organization + +The active organization on mobile mirrors the behavior used on the [web app](/docs/web/organizations/active-organization) and in the [extension](/docs/extension/organizations). It is tracked in the authenticated session as `activeOrganizationId` and used to scope all organization-bound data and actions. + +Below you can find how to read and work with the active organization in your mobile app context. + +## Reading the active organization + +Use your auth client's helper to read the active organization from the session. This keeps the client in sync with the server and avoids duplicating tenancy logic. + +```tsx title="organizations.tsx" +import { authClient } from "~/lib/auth"; + +export function OrganizationsScreen() { + const organization = authClient.useActiveOrganization(); + const member = authClient.useActiveMember(); + + return ( + <> + {organization?.name} + {member?.role} + + ); +} +``` + +This mirrors the [extension](/docs/extension/organizations) approach and the [web hook](/docs/web/organizations/active-organization), ensuring the active organization and member role stay consistent with the server session. + +## Performing actions + +When invoking API routes from the mobile app, prefer passing the `organizationId` explicitly with the payload. This guarantees the correct tenant is targeted even if multiple devices or views are active simultaneously. + +```tsx title="create-post.tsx" +import { api } from "~/lib/api"; + +export function CreatePost() { + const activeOrganization = authClient.useActiveOrganization(); + + const { mutate } = useMutation({ + mutationFn: async (post: PostInput) => + api.posts.$post({ + ...post, + organizationId: activeOrganization?.id, + }), + }); + + return ( +
+ +
+ ); +} +``` + +This mirrors the recommendation from the [web guide](/docs/web/organizations/active-organization#api-route) and avoids edge cases tied to stale session values. + +## Switching organizations + +TurboStarter ships an account switcher out of the box for mobile. You can drop it into your app and customize labels and styling as needed. + +```tsx title="settings.tsx" +import { AccountSwitcher } from "~/modules/organization/account-switcher"; + +export function SettingsScreen() { + return ; +} +``` + +When a user selects a new organization, it calls your backend to update the session's `activeOrganizationId` and then re-read the session or invalidate related queries. + +For deeper background on how the active organization is resolved, see the [web guide](/docs/web/organizations/active-organization). diff --git a/.context/turbostarter-framework-context/sections/mobile/organizations/invitations.md b/.context/turbostarter-framework-context/sections/mobile/organizations/invitations.md new file mode 100644 index 0000000..98cd8fe --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/organizations/invitations.md @@ -0,0 +1,58 @@ +--- +title: Invitations +description: Send, track, and accept organization invites. +url: /docs/mobile/organizations/invitations +--- + +# Invitations + +Invite teammates by email to join an organization directly from your mobile app. Acceptance is straightforward: we verify the invite, create or reuse the membership with the intended role, and set the user's active organization. + +The implementation uses the same APIs and rules as the [web app](/docs/web/organizations/invitations) and is powered by the [Better Auth organization plugin](https://www.better-auth.com/docs/plugins/organization). + +![Mobile invitations list](/images/docs/mobile/organizations/invitations/list.png) + +## Capabilities + +* Send invitations by email. +* View and filter invitations by status or role, and search by email. +* Resend or revoke an invitation. +* Accept an invitation via a [deep link](https://docs.expo.dev/linking/into-your-app/). + + + Permissions are enforced by roles. Typically, only organization admins can + send or manage invites. See [RBAC (Roles & + Permissions)](/docs/mobile/organizations/rbac). + + +## Inviting members + +Sending an invitation typically requires the invitee's email and the intended role. You can add multiple recipients in the invitation form to invite several members at once. + +![Invite members bottom sheet](/images/docs/mobile/organizations/invitations/invite.png) + +After sending, the invitee receives an email with a link to accept. It's a [deep link](https://docs.expo.dev/guides/linking) that opens your app and automatically validates the invite. + +## Handling invitations + +When a recipient opens an invite link on their device, the app automatically handles the entire flow - reading, parsing, and validating the invite - for you. + +![Join organization prompt](/images/docs/mobile/organizations/invitations/join.png) + +When the user accepts, we create or reuse their membership and set the active organization in their session. If they reject the invite, we redirect them to their account home. + +## Learn more + +For underlying details shared across platforms, see the web documentation: + + + + + + + + + + + +These cover the schema, token lifecycle, and admin tooling shared by the mobile and web apps. diff --git a/.context/turbostarter-framework-context/sections/mobile/organizations/overview.md b/.context/turbostarter-framework-context/sections/mobile/organizations/overview.md new file mode 100644 index 0000000..3fce277 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/organizations/overview.md @@ -0,0 +1,77 @@ +--- +title: Overview +description: Learn how to use organizations/teams/multi-tenancy in TurboStarter mobile app. +url: /docs/mobile/organizations/overview +--- + +# Overview + +Organizations let you build teams and multi-tenant SaaS out of the box in the mobile app. + +Users can create organizations, invite teammates, assign roles, and seamlessly switch between workspaces — all from iOS/Android — with the same secure data isolation used on the [web app](/docs/web/organizations/overview). + + + [Multi-tenancy](https://www.ibm.com/think/topics/multi-tenant) is a software architecture pattern where a single instance of an application serves multiple tenants, each with its own data and configuration. + + +The feature is powered by the same [Better Auth organization plugin](https://www.better-auth.com/docs/plugins/organization) and shares TurboStarter's API, routing, and data layer with the [web app](/docs/web/organizations/overview) and [extension](/docs/extension/organizations). That means your mobile app benefits from the same tenancy rules, RBAC checks, and invitations flow without duplicating backend logic. + + + +## Architecture + +On mobile, TurboStarter uses the same pragmatic multi-tenant architecture as the [web app](/docs/web/organizations/overview): + +* **Tenant context** lives in the authenticated session as the active organization ID. The mobile client reads this context from the API and includes it when making requests. +* **Data scoping** is performed server-side via `organizationId` on tenant-owned tables and guard clauses in queries. Mobile screens consume scoped endpoints so users only see data for their selected organization. +* **Authorization** combines tenant scoping with role checks. We separate “can access this tenant?” from “can perform this action within the tenant?”. +* **Extensibility**: add new tenant-bound entities by including `organizationId` in your schema and using the provided helpers to read or switch the active organization in the app. + +This keeps data isolated per organization while remaining simple to reason about across platforms. + + + For deeper details on the shared data model used by the mobile app, see [Data + model](/docs/web/organizations/data-model). + + +## Concepts + +The same core concepts apply in the mobile app: + +| Concept | Description | +| ----------------------- | -------------------------------------------------------------------------------------------------- | +| **Organization** | A workspace that owns resources and settings, acting as an isolated tenant. | +| **Member** | A user assigned to an organization. | +| **Role** | Access level within an organization (see [RBAC](/docs/mobile/organizations/rbac)). | +| **Invitation** | Email request to join an organization (see [Invitations](/docs/mobile/organizations/invitations)). | +| **Active organization** | The currently selected organization in a user's session, used to scope data and permissions. | + +These concepts provide the building blocks for flexible team management and secure, multi-tenant SaaS applications on mobile. + +## Development data + +In development, TurboStarter automatically [seeds](/docs/mobile/installation/commands#seeding-database) example data when you set up services. The mobile app connects to the same development API, so you can test the full organizations flow end-to-end: + +* One organization is created by default. +* All default roles are created and assigned within that organization. +* Sample invitations are generated so you can test the invite flow. + +You can safely experiment with these sample organizations, roles, and invitations to understand multi-tenancy features — [reset](/docs/mobile/installation/commands#resetting-database) or [reseed](/docs/mobile/installation/commands#seeding-database) anytime to return to the default state. + +The default credentials for demo users can be customized using the `SEED_EMAIL` and `SEED_PASSWORD` environment variables. + + + The default development data and setup are intended for local development and + testing only. **Never** use these seeds or configurations in a production + environment - they are insecure and may expose sensitive functionality. + + +## Customization + +You have flexibility to adapt organizations to fit your mobile experience. For example, you might rename labels (such as Organization to *Team* or *Workspace*), and update the app copy accordingly. + +You can adjust the available [roles and permissions](/docs/mobile/organizations/rbac) to suit your access model. + +The [invitation flow](/docs/mobile/organizations/invitations) can be customized, including how verification, onboarding, or metadata capture work. + +Feel free to check how to configure all of these features inside mobile application in the dedicated sections linked above. diff --git a/.context/turbostarter-framework-context/sections/mobile/organizations/rbac.md b/.context/turbostarter-framework-context/sections/mobile/organizations/rbac.md new file mode 100644 index 0000000..88f5d16 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/organizations/rbac.md @@ -0,0 +1,115 @@ +--- +title: RBAC (Roles & Permissions) +description: Manage roles, permissions, and access scopes. +url: /docs/mobile/organizations/rbac +--- + +# RBAC (Roles & Permissions) + +Role-based access control (RBAC) lets you define who can do what in an organization. + + + If you're new to the RBAC concept, a simple mental model is: + + * Users belong to organizations. + * Users get roles. + * Roles map to permissions on resources. + + +In TurboStarter, we primarily rely on the [Better Auth plugin](https://www.better-auth.com/docs/plugins/organization) for the heavy lifting—roles, permissions, teams, and member management—while handling critical logic with our own code. + +This provides a flexible access control system, letting you control user access based on their role in the organization. You can also define custom permissions per role. + + + TurboStarter ships with the default RBAC system configured out of the box. This setup may be enough if you're not planning a very complex access control system, but you can also easily customize it to your needs. + + On mobile, use conditional UI (disable or hide actions) together with client helpers to match each member's role. + + +## Roles + +Roles are named bundles of permissions. Keep them few and well-defined. By default, we have the following roles: + +```ts +const MemberRole = { + MEMBER: "member", + ADMIN: "admin", + OWNER: "owner", +} as const; +``` + +A user can have multiple roles in an organization. For example, a user can be a member and an admin (if it makes sense for your application). + + + The organization's `admin` role is different from the user's global `admin` role. + + The organization `admin` governs permissions only inside the organization, whereas the global `admin` controls access to the [super admin dashboard](/docs/web/admin/overview). + + +To create additional roles with custom permissions, see the [official documentation](https://www.better-auth.com/docs/plugins/organization#create-access-control) for more details. + +## Permissions + +Permissions represent what actions a role can perform on which resources. + +To check if the current user has permission to perform an action on mobile, use the client helper and handle the boolean result in your component logic. + +```tsx title="create-project.tsx" +import { useQuery, useMutation } from "@tanstack/react-query"; +import { authClient } from "~/lib/auth"; + +export function CreateProject() { + const { data: canCreate } = useQuery({ + queryKey: ["permission", "project", "create"], + queryFn: () => + authClient.organization.hasPermission({ + permissions: { project: ["create"] }, + }), + }); + + const { mutate, isPending } = useMutation({ + mutationFn: async () => { + // perform the create action + }, + }); + + return ( + + ); +} +``` + +When you already have the active member's role, prefer the client-side `checkRolePermission` to avoid extra API calls. + +```tsx title="update-project.tsx" +import { authClient } from "~/lib/auth"; + +export function UpdateProject() { + const activeMember = authClient.useActiveMember(); + + const canUpdate = authClient.organization.checkRolePermission({ + permission: { + project: ["update"], + }, + role: activeMember.role, + }); + + return ; +} +``` + +We leverage the existing hook to retrieve the active member role within the [active organization](/docs/mobile/organizations/active-organization) context. That way, you can easily check whether a member has permission to perform an action without a server round trip. + + + This does not include any dynamic roles or permissions because everything runs synchronously on the client-side. Use the `hasPermission` APIs to include checks for dynamic roles and permissions. + + +If you need to add more granular permissions to existing roles, or create new ones, use the [`createAccessControl`](https://www.better-auth.com/docs/plugins/organization#custom-permissions) API. + +For further customization—such as dynamic access control, lifecycle hooks, or team management—see the guidance in the [official documentation](https://www.better-auth.com/docs/plugins/organization) and the [web guide](/docs/web/organizations/rbac). diff --git a/.context/turbostarter-framework-context/sections/mobile/publishing/android.md b/.context/turbostarter-framework-context/sections/mobile/publishing/android.md new file mode 100644 index 0000000..c9d9b6f --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/publishing/android.md @@ -0,0 +1,164 @@ +--- +title: Google Play (Android) +description: Learn how to publish your mobile app to the Google Play Store. +url: /docs/mobile/publishing/android +--- + +# Google Play (Android) + +[Google Play](https://play.google.com/) is the primary platform for distributing Android apps to billions of users worldwide. It's a powerful marketplace that allows you to reach a large audience and monetize your app. + +To submit your app to the Play Store, you'll need to follow a series of steps. We'll walk through those steps here. + + + Before you submit, review the publishing [guidelines](/docs/mobile/marketing) and confirm that your app meets Google's policies to avoid common rejections. + + +## Developer account + +A Google Play Developer account is required to submit your app to the Google Play Store. You can sign up on the [Google Play Console](https://play.google.com/console/) and pay the one-time registration fee. + +![Google Play Developer Account](/images/docs/mobile/publishing/android/developer-account.png) + +To publish apps to Google Play, you must verify your identity. See the [official guide](https://support.google.com/googleplay/android-developer/answer/14177239) for more information. Next, you'll need to create a new app in the [Google Play Console](https://play.google.com/apps/publish/) by clicking the *Create app* button. + +## Submission + +After registering your developer account, setting it up, and preparing your app, you're ready to publish it to the Play Store. + +There are multiple ways to submit your app: + +* **Manual submission:** Upload your app bundle directly to the Play Store via the Play Console. +* **Local submission:** Use [EAS CLI](https://github.com/expo/eas-cli) to submit your app. +* **CI/CD submission:** Use ready-to-use GitHub Actions workflow to automatically submit your app. + +**The first submission must be done manually, while subsequent updates can be submitted automatically.** We'll go through each approach in detail below. + +### Manual submission + +This approach is not recommended, as it is more error-prone and time-consuming due to manual steps. However, it's still the **only way to submit your app for the first time**. You can also use this route if you need to upload a build without EAS Submit (for example, during service maintenance) or if you prefer a fully manual flow. + +**Create the app entry in Google Play Console** + +1. Visit [Google Play Console](https://play.google.com/console/) and sign in. Accept any pending agreements if prompted. +2. Click *Create app*, then enter your app name, default language, app type, and pricing (free/paid). Confirm policy declarations. +3. Finish initial setup tasks (App access, Ads, Content rating, Target audience, Data safety, Privacy policy URL). + +**Upload the `.aab` file to a track (internal/closed/open/production)** + +1. The fastest route for a first upload is often *Internal testing*. Go to *Internal testing* → *Releases* (or choose *Closed/Open/Production*), then click *Create new release*. +2. Upload the `.aab` file, add release notes, and review any warnings. +3. Save and continue through the checks until you're ready to submit for review or roll out to [testers](https://play.google.com/console/about/internal-testing/). + +**Verify and submit for review** + +1. Complete Store listing assets and metadata if not already done. +2. Resolve any policy warnings. When ready, start the rollout to request a [review](/docs/mobile/publishing/android#review). + +After your first manual upload is accepted, you can use [Local submission](/docs/mobile/publishing/android#local-submission) or [CI/CD submission](/docs/mobile/publishing/android#cicd-submission-recommended) for subsequent releases. + +For more information, please refer to the guides listed below. + + + + + + + +### Local submission + + + Due to Google Play API limitations, you must upload your app to Google Play **manually at least once** (to any track: internal, closed, open, or production) before automated submissions will work. See the detailed walkthrough in the ["First Android submission" guide](https://github.com/expo/fyi/blob/main/first-android-submission.md). + + +First, you need to **upload and configure a Google Service Account Key with EAS**. This is the required first step to submit your Android app to the Google Play Store. Follow the [guide on uploading a Google Service Account Key for Play Store submissions with EAS](https://github.com/expo/fyi/blob/main/creating-google-service-account.md) for detailed instructions. + +Next, you have to get your app bundle — if you followed the [checklist](/docs/mobile/publishing/checklist), you should have the `.aab` file in your app folder from the [build step](/docs/mobile/publishing/checklist#build-your-app). If you used GitHub Actions to build your app, you can find the results in the `Builds` tab of your [EAS project](https://expo.dev). Download the artifacts and save them on your local machine. + +Then, navigate to your app folder and run the following command to submit your app to the Play Store: + +```bash +eas submit --platform android +``` + +The command will guide you through the submission process. You can also configure the steps of the submission process by adding a submission profile in `eas.json`. + + + If you upload your Google Service Account key to EAS credentials, you do not need to reference a local file path anywhere. + + +To speed up the submission process, you can use the `--auto-submit` flag to automatically submit a build after it is built: + +```bash +eas build --platform android --auto-submit +``` + +This will automatically submit the build with all the required credentials to the Play Store right after it is built. + + + + + + + + + +### CI/CD submission (recommended) + + + Due to Google Play API limitations, you must upload your app to Google Play **manually at least once** (to any track: internal, closed, open, or production) before automated submissions will work. See the detailed walkthrough in the ["First Android submission" guide](https://github.com/expo/fyi/blob/main/first-android-submission.md). + + +TurboStarter comes with a pre-configured GitHub Actions workflow to automatically submit your mobile app to the Play Store. You'll find the workflow in the `.github/workflows/publish-mobile.yml` file. + +To use this workflow, [upload your Google Play Service Account key to EAS](https://github.com/expo/fyi/blob/main/creating-google-service-account.md) and check your Android credentials setup by running: + +```bash +eas credentials --platform android +``` + +This way, you avoid storing the JSON key in your repository or CI/CD provider. + + + This workflow also requires a [personal access token](https://docs.expo.dev/accounts/programmatic-access/#personal-access-tokens) for your Expo account. Add it as `EXPO_TOKEN` in your [GitHub repository secrets](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions), which will allow the `eas submit` command to run. + + +That's it! After completing these steps, [trigger the workflow](https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/manually-running-a-workflow) to submit your new build to the Play Store automatically 🎉 + + + + + + + + + +## Review + +After filling out the information about your item, you're ready to submit it for review. Click on the *Send for review* button and confirm that you want to proceed with the submission: + +![Send for review](/images/docs/mobile/publishing/android/send-for-review.png) + +To control **when** your app is released after review, you can configure [Managed publishing](https://support.google.com/googleplay/android-developer/answer/9859654) in the Google Play Console. + +After submitting your app for review, it will enter Google's review process. The review time may vary depending on your app, and you'll receive a notification when the status updates. For more details, check out the [Google Play Review Process](https://developers.google.com/workspace/marketplace/about-app-review) documentation. + + + If your submission is rejected, you'll receive an email from Google with the rejection reason. You'll need to fix the issues and upload a new version of your app. + + ![Google Play Rejection](/images/docs/mobile/publishing/android/rejection.png) + + Make sure to follow the [guidelines](/docs/mobile/marketing) or check [publishing troubleshooting](/docs/mobile/troubleshooting/publishing) for more info. + + +When your app is approved by Google, you'll be able to publish it on the Play Store. + +![Your update is live email from Google](/images/docs/mobile/publishing/android/update-live.png) + +You can learn more about the review process in the official guides listed below. + + + + + + diff --git a/.context/turbostarter-framework-context/sections/mobile/publishing/checklist.md b/.context/turbostarter-framework-context/sections/mobile/publishing/checklist.md new file mode 100644 index 0000000..1137ae2 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/publishing/checklist.md @@ -0,0 +1,212 @@ +--- +title: Checklist +description: Let's publish your TurboStarter app to stores! +url: /docs/mobile/publishing/checklist +--- + +# Checklist + +When you're ready to publish your TurboStarter app to stores, follow this checklist. + +This process may take a few hours and some trial and error, so buckle up - you're almost there! + + + + ## Create database instance + + **Why it's necessary?** + + A production-ready database instance is essential for storing your application's data securely and reliably in the cloud. [PostgreSQL](https://www.postgresql.org/) is the recommended database for TurboStarter due to its robustness, features, and wide support. + + **How to do it?** + + You have several options for hosting your PostgreSQL database: + + * [Supabase](/docs/mobile/recipes/supabase) - Provides a fully managed Postgres database with additional features + * [Vercel Postgres](https://vercel.com/storage/postgres) - Serverless SQL database optimized for Vercel deployments + * [Neon](https://neon.tech/) - Serverless Postgres with automatic scaling + * [Turso](https://turso.tech/) - Edge database built on libSQL with global replication + * [DigitalOcean](https://www.digitalocean.com/products/managed-databases) - Managed database clusters with automated failover + + Choose a provider based on your needs for: + + * Pricing and budget + * Geographic region availability + * Scaling requirements + * Additional features (backups, monitoring, etc.) + + + + ## Migrate database + + **Why it's necessary?** + + Pushing database migrations ensures that your database schema in the remote database instance is configured to match TurboStarter's requirements. This step is crucial for the application to function correctly. + + **How to do it?** + + You basically have two possibilities of doing a migration: + + + + TurboStarter comes with predefined Github Action to handle database migrations. You can find its definition in the `.github/workflows/publish-db.yml` file. + + What you need to do is to set your `DATABASE_URL` as a [secret for your Github repository](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions). + + Then, you can run the workflow which will publish the database schema to your remote database instance. + + [Check how to run Github Actions workflow.](https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/manually-running-a-workflow) + + + + You can also run your migrations locally, although this is not recommended for production. + + To do so, set the `DATABASE_URL` environment variable to your database URL (that comes from your database provider) in `.env.local` file and run the following command: + + ```bash + pnpm with-env pnpm --filter @turbostarter/db db:migrate + ``` + + This command will run the migrations and apply them to your remote database. + + [Learn more about database migrations.](/docs/web/database/migrations) + + + + + + ## (Optional) Set up Firebase project + + **Why it's necessary?** + + Setting up a Firebase project is optional, and depends on which features your app is using. For example, if you want to use [Analytics](/docs/mobile/analytics/overview) with [Google Analytics](/docs/mobile/analytics/configuration#google-analytics), setting up a Firebase project is required. + + **How to do it?** + + Please refer to the [Firebase project](/docs/mobile/installation/firebase) section on how to set up and configure your Firebase project. + + + + ## Set up web backend API + + **Why it's necessary?** + + Setting up the backend is necessary to have a place to store your data and to have other features work properly (e.g. authentication, billing or storage). + + **How to do it?** + + Please refer to the [web deployment checklist](/docs/web/deployment/checklist) on how to set up and deploy the web app backend to production. + + + + ## Environment variables + + **Why it's necessary?** + + Setting the correct environment variables is essential for the application to function correctly. These variables include API keys, database URLs, and other configuration details required for your app to connect to various services. + + **How to do it?** + + Use our `.env.example` files to get the correct environment variables for your project. Then add them to your project on the [EAS platform](https://docs.expo.dev/eas/environment-variables/) for correct profile and environment: + + ![EAS environment variables](/images/docs/mobile/eas-environment-variables.png) + + Alternatively, you can add them to your `eas.json` file under correct profile. + + ```json title="eas.json" + { + "profiles": { + "base": { + "env": { + "EXPO_PUBLIC_DEFAULT_LOCALE": "en", + "EXPO_PUBLIC_AUTH_PASSWORD": "true", + "EXPO_PUBLIC_AUTH_MAGIC_LINK": "false", + "EXPO_PUBLIC_THEME_MODE": "system", + "EXPO_PUBLIC_THEME_COLOR": "orange" + } + }, + "production": { + "extends": "base", + "autoIncrement": true, + "env": { + "APP_ENV": "production", + "EXPO_PUBLIC_SITE_URL": "https://www.turbostarter.dev", + } + } + } + } + ``` + + + + ## Build your app + + + Building your app requires an EAS account and project. If you don't have one, you can create it by following the steps [here](https://expo.dev/eas). + + + **Why it's necessary?** + + Building your app is necessary to create a standalone application bundle that can be published to the stores. + + **How to do it?** + + You basically have two possibilities to build a bundle for your app: + + + + TurboStarter comes with predefined Github Action to handle building your app on EAS. You can find its definition in the `.github/workflows/publish-mobile.yml` file. + + What you need to do is to set your `EXPO_TOKEN` as a [secret for your Github repository](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions). You can obtain it from your EAS account, in the [Access Tokens](https://expo.dev/settings/access-tokens) section. + + Then, you can run the workflow which will build the app on [EAS platform](https://expo.dev/eas). + + [Check how to run Github Actions workflow.](https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/manually-running-a-workflow) + + + + You can also run your build locally, although this is not recommended for production. + + To do it, you'll need to have [EAS CLI](https://github.com/expo/eas-cli) installed on your machine. You can install it by running the following command: + + ```bash + npm install -g eas-cli + ``` + + Then, run the following command to build your app with the `production` profile: + + ```bash + eas build --profile production --platform all + ``` + + This will build the app for both platforms (iOS and Android) and output the results in your app folder. + + + + + + ## Submit to stores + + **Why it's necessary?** + + Releasing your app to the stores is essential for making it accessible and discoverable by your users. This allows users to find, install, and trust your application through official channels. + + **How to do it?** + + We've prepared dedicated guides for each store that TurboStarter supports out-of-the-box, please refer to the following pages: + + + + + + + + + +That's it! Your app is now live and accessible to your users, good job! 🎉 + + + * Optimize your store listings with compelling descriptions, keywords, screenshots and preview videos + * Remove placeholder content and replace with your final production content + * Update all visual branding including favicon, scheme, splash screen and app icons + diff --git a/.context/turbostarter-framework-context/sections/mobile/publishing/ios.md b/.context/turbostarter-framework-context/sections/mobile/publishing/ios.md new file mode 100644 index 0000000..e786428 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/publishing/ios.md @@ -0,0 +1,215 @@ +--- +title: App Store (iOS) +description: Learn how to publish your mobile app to the Apple App Store. +url: /docs/mobile/publishing/ios +--- + +# App Store (iOS) + +[Apple App Store](https://www.apple.com/app-store/) is the primary platform for distributing iOS apps, making them available on iPhones, iPads, and other Apple devices to millions of users worldwide. + +To submit your app to the App Store, you'll need to follow a series of steps. We'll walk through those steps here. + + + Before you submit, review the publishing [guidelines](/docs/mobile/marketing) and confirm that your app meets Apple's policies to avoid common rejections. + + +## Developer account + +An Apple Developer account is required to submit your app to the Apple App Store. You can sign up for an Apple Developer account on the [Apple Developer Portal](https://developer.apple.com/account/). + +![Apple Developer Account](/images/docs/mobile/publishing/ios/developer-account.png) + +To submit apps to the App Store, you must also be a member of the Apple Developer Program. You can join the program by paying the annual fee. + +## Submission + +There are two primary ways to submit your iOS app to the App Store: + +* **Manual:** Uploading the build yourself through Apple's tools, such as [Transporter](https://apps.apple.com/app/transporter/id1450874784) or [Xcode](https://developer.apple.com/xcode/). +* **Automatic (recommended):** Using [EAS Submit](/docs/mobile/publishing/ios#local-submission) or [CI/CD](/docs/mobile/publishing/ios#cicd-submission-recommended), which simplifies the process, ensures consistency, and reduces manual error. + +Below, you'll find guidance for both submission methods—choose the one that fits your workflow and project needs. + +### Manual submission + +This approach is not recommended, as it is more error-prone and time-consuming due to manual steps. Use this route if you need to upload a build without EAS Submit (for example, during service maintenance) or prefer a fully manual flow from macOS. + +**Create the app entry in App Store Connect** + +1. Visit [App Store Connect](https://appstoreconnect.apple.com/) and sign in. Accept any pending agreements if prompted. +2. From Apps, click the + button and select *New App*. +3. Enter the app name, primary language, bundle identifier, and a unique SKU (for example, your bundle ID, such as `com.company.myapp`). +4. Press Create to finish setting up the app record. + +**Upload the IPA with Transporter** + +1. Install [Apple's Transporter](https://apps.apple.com/app/transporter/id1450874784) from the Mac App Store. +2. Open Transporter and sign in with your Apple ID. +3. Drag the `.ipa` into Transporter (or click *Add App* to choose the file). +4. Press *Deliver* to upload. Transfer time varies by file size and network. + +**Verify processing and select the build** + +1. Once uploaded, Apple processes the binary (often 10-20 minutes). +2. Back in [App Store Connect](https://appstoreconnect.apple.com/), open My Apps and select your app. +3. Under the *App Store* tab, select the new build in the *Build* section. If it's missing, wait and refresh. +4. Proceed with the usual App Store steps (screenshots, metadata, compliance, then submit for review). + +For more information about the required metadata, refer to the official guides. + + + + + + + +### Local submission + +If you followed the [checklist](/docs/mobile/publishing/checklist), you should have the `.ipa` file in your app folder from the [build step](/docs/mobile/publishing/checklist#build-your-app). If you used GitHub Actions to build your app, you can find the results in the `Builds` tab of your [EAS project](https://expo.dev). Download the artifacts and save them on your local machine. + +Then, navigate to your app folder and run the following command to submit your app to the App Store: + +```bash +eas submit --platform ios +``` + +The command will guide you through the submission process. You can configure the submission process by adding a submission profile in `eas.json`: + +```json title="eas.json" +{ + "submit": { + "production": { + "ios": { + "ascAppId": "your-app-store-connect-app-id" + } + } + } +} +``` + + + + 1. Sign in to [App Store Connect](https://appstoreconnect.apple.com/) and choose your team. + 2. Open the [Apps](https://appstoreconnect.apple.com/apps) area. + 3. Select your app from the list. + 4. Switch to the *App Store* tab. + 5. Go to *General* → *App Information*. + 6. In *General Information*, the value labeled *Apple ID* is your `ascAppId`. + + ![App Store Connect App Information](/images/docs/mobile/publishing/ios/asc-app-id.png) + + + +To speed up the submission process, you can use the `--auto-submit` flag to automatically submit a build after it is built: + +```bash +eas build --platform ios --auto-submit +``` + +This will automatically submit the build with all the required credentials to the App Store right after it is built. + + + + + + + +### CI/CD submission (recommended) + +TurboStarter comes with a pre-configured GitHub Actions workflow to submit your mobile app to the App Store automatically. It's located in the `.github/workflows/publish-mobile.yml` file. + +To be able to use this workflow, you'd need to fulfill the following prerequisites: + +1. **Configure your App Store Connect API Key** + + Run the following command to configure your App Store Connect API Key: + + ```bash + eas credentials --platform ios + ``` + + The command will prompt you to configure credentials: + + 1. Choose the `production` build profile. + 2. Authenticate with your Apple Developer account and proceed through the prompts. + 3. Pick **App Store Connect → Manage your API Key**. + 4. Enable **Use an API Key for EAS Submit** for the project. + +2. **Provide a submission profile in `eas.json`** + + Next, add a submission profile in `eas.json` with the following: + + ```json title="eas.json" + { + "submit": { + "production": { + "ios": { + "ascAppId": "your-app-store-connect-app-id" + } + } + } + } + ``` + + + + 1) Log into [App Store Connect](https://appstoreconnect.apple.com/) under the correct team. + 2) Go to [Apps](https://appstoreconnect.apple.com/apps) and open your app. + 3) Ensure the *App Store* tab is selected. + 4) Navigate to *General* → *App Information*. + 5) Copy the value shown as *Apple ID* — that is the `ascAppId`. + + ![App Store Connect App Information](/images/docs/mobile/publishing/ios/asc-app-id.png) + + + + + This workflow also requires a [personal access token](https://docs.expo.dev/accounts/programmatic-access/#personal-access-tokens) for your Expo account. Add it as `EXPO_TOKEN` in your [GitHub repository secrets](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions), which will allow the `eas submit` command to run. + + +That's it! After completing these steps, [trigger the workflow](https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/manually-running-a-workflow) to submit your new build to the App Store automatically 🎉 + + + + + + + +## Review + +After completing your app information, you're ready to submit it for review. Click the *Add for review* button and confirm that you want to proceed with the submission: + +![Confirm submission](/images/docs/mobile/publishing/ios/confirm-submission.png) + +On the *Distribution* tab, you can configure the release process after the review is complete — whether you want to release the app automatically after review, later, or manually. + +![App Store Connect Version Release](/images/docs/mobile/publishing/ios/version-release.png) + +Once you've submitted your app for review, it will go through Apple's review process. The duration can vary based on the specifics of your app and you'll be notified when the status changes. For more information, refer to the [App Review](https://developer.apple.com/distribute/app-review/) docs. + + + If your submission is rejected, you'll receive an email from Apple with the rejection reason. You'll need to fix the issues and upload a new version of your app. + + ![App Store Connect Rejection](/images/docs/mobile/publishing/ios/rejection.png) + + Make sure to follow the [guidelines](/docs/mobile/marketing) or check [publishing troubleshooting](/docs/mobile/troubleshooting/publishing) for more information. + + If you need to clarify anything with Apple, you can reply to the app review request in App Store Connect: + + ![App Store Connect Reply to Review](/images/docs/mobile/publishing/ios/reply-to-review.png) + + This helps you understand the rejection and what you need to change to make your app eligible for distribution. + + +When your app is approved by Apple (by email or push notification), you'll be able to publish it on the App Store. + +![Review notification](/images/docs/mobile/publishing/ios/review-notifications.jpeg) + +You can learn more about the review process in the official guides listed below. + + + + + + diff --git a/.context/turbostarter-framework-context/sections/mobile/publishing/updates.md b/.context/turbostarter-framework-context/sections/mobile/publishing/updates.md new file mode 100644 index 0000000..44cb34f --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/publishing/updates.md @@ -0,0 +1,50 @@ +--- +title: Updates +description: Learn how to update your published app. +url: /docs/mobile/publishing/updates +--- + +# Updates + +After you publish your app to the stores, you can release updates to provide your users with new features and bug fixes. + +TurboStarter offers two ready-to-use methods for updating your apps; we'll walk through both of them below. + +## Over-the-air (OTA) updates + +[Over-the-air (OTA) updates](https://en.wikipedia.org/wiki/Over-the-air_update) allow you to push updates to your app without requiring users to download a new version from the app store. This powerful feature enables rapid iteration and quick fixes. + +![OTA updates](/images/docs/mobile/ota-updates.png) + +TurboStarter integrates with [EAS Update](https://docs.expo.dev/eas-updates/overview/) to provide you with a seamless experience for managing your app updates. We also shipped a native notification that you can use to notify your users about the new updates available. + +Then, to push your update straight to your users, you'll just need to run single command: + +```bash +eas update --channel [channel-name] --message "[message]" +``` + +The app will automatically download the update in the background and install it when your users are ready. You can also configure the update channel and message to be displayed to your users. + +Feel free to check the [official documentation](https://docs.expo.dev/eas-update/getting-started/) for more information. + + + OTA updates are **only supported for non-native changes**. If you need to update your app with a new native feature (or add a package that uses native dependencies), you'll need to submit a new version to the stores - see below for more details. + + +## Submitting a new version + +The most traditional way to update your app is to submit a new version to the stores. This is the most reliable approach, but it can take some time for the new version to be approved and made available to users. + +To submit a new version, update the version number in both your `package.json` file and your `app.config.ts` file. + +```json +{ + ... + "version": "1.0.0", // [!code --] + "version": "1.0.1", // [!code ++] + ... +} +``` + +Next, follow the exact same steps as [when you initially published your app](/docs/mobile/publishing/checklist). When you submit your app for review, be sure to include release notes for the new version. diff --git a/.context/turbostarter-framework-context/sections/mobile/push-notifications.md b/.context/turbostarter-framework-context/sections/mobile/push-notifications.md new file mode 100644 index 0000000..aa8327d --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/push-notifications.md @@ -0,0 +1,13 @@ +--- +title: Push notifications +description: Engage your users with personalized notifications. +url: /docs/mobile/push-notifications +--- + +# Push notifications + + + We are working on push notifications to help you engage your users. Stay tuned for updates. + + [See roadmap](https://github.com/orgs/turbostarter/projects/1) + diff --git a/.context/turbostarter-framework-context/sections/mobile/recipes/supabase.md b/.context/turbostarter-framework-context/sections/mobile/recipes/supabase.md new file mode 100644 index 0000000..a7b6c5a --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/recipes/supabase.md @@ -0,0 +1,221 @@ +--- +title: Supabase +description: Learn how to set up Supabase as the database (and optional storage) provider for your TurboStarter project. +url: /docs/mobile/recipes/supabase +--- + +# Supabase + +[Supabase](https://supabase.com) is an open-source backend platform built on top of PostgreSQL that provides a managed database, storage, and other features out of the box. + +You can adopt Supabase incrementally - start with just the pieces you need (for example, database only, or database + storage) and add more features over time. There's no requirement to integrate everything at once. + +In this guide, we'll walk you through the process of setting up Supabase as a provider for your TurboStarter project. This could include using it as a [database](https://supabase.com/docs/guides/database), [storage](https://supabase.com/docs/guides/storage), [edge runtime for your API](https://supabase.com/docs/guides/functions) and more. + +## Prerequisites + +Before you start, make sure you have: + +* **TurboStarter project** cloned locally with dependencies installed (you can use our [CLI](/docs/web/cli) to create a new project in seconds) +* **Supabase account** - you can create one at [supabase.com](https://supabase.com/sign-up) +* Basic familiarity with the core database docs: + * [Database overview](/docs/web/database/overview) + * [Migrations](/docs/web/database/migrations) + * [Database client](/docs/web/database/client) + + + + ## Create a new Supabase project + + 1. Go to the [Supabase dashboard](https://supabase.com). + 2. Create a **new project** (choose a strong database password and a region close to your users). + 3. Supabase will automatically provision a **PostgreSQL database** for you. + + ![Create a new Supabase project](/images/docs/web/recipes/supabase/create-project.png) + + Optionally, you can customize the **Security options** by choosing the **Only Connection String** option - it will opt out of autogenerating API for tables inside your database. It's not needed for TurboStarter setup, but of course you can still leverage it for your custom use-cases. + + ![Security options](/images/docs/web/recipes/supabase/security-options.png) + + Once the project is ready, you can fetch the connection string. + + + + ## Get the database connection string + + In the Supabase dashboard: + + 1. Open your project. + 2. Click on the **Connect** button at the top. + 3. Locate the **connection string** for your chosen ORM (it will be under the **ORMs** tab). + + ![Connect application](/images/docs/web/recipes/supabase/connect-app.png) + + Copy this value - you'll use it as your `DATABASE_URL`. + + + In your Supabase connection string, you can see a placeholder like `[YOUR-PASSWORD]`. Make sure to replace this with the actual password you set when creating your Supabase project. + + + + + ## Configure environment variables + + TurboStarter reads database connection settings from the **root** `.env.local` file and uses them inside the `@turbostarter/db` package. + + Create (or update) the `.env.local` file in the **monorepo root**: + + ```dotenv title=".env.local" + DATABASE_URL="postgres://postgres.[YOUR-PROJECT-REF]:[YOUR-PASSWORD]@aws-0-[aws-region].pooler.supabase.com:6543/postgres?pgbouncer=true&connection_limit=1" + ``` + + Replace: + + * `YOUR-PROJECT-REF` with your Supabase project ref + * `YOUR-PASSWORD` with the database password you set when creating the project + * `aws-region` with the region shown in the Supabase connection string + + + These variables are validated in the `@turbostarter/db` package and used to create Drizzle client for your database. + + + For more background on how `DATABASE_URL` is used, see [Database overview](/docs/web/database/overview). + + + + ## Setup your Supabase database + + With `DATABASE_URL` now pointing to Supabase, you can apply the existing TurboStarter schema to your Supabase database. + + From the monorepo root, run: + + ```bash + pnpm with-env pnpm --filter @turbostarter/db db:migrate + ``` + + This will: + + * Use your Supabase `DATABASE_URL` from `.env.local` + * Run all pending SQL migrations from `packages/db/migrations` + * Create the full TurboStarter schema (users, billing, demo tables, etc.) in Supabase + + If you're actively iterating on the schema, you can generate new migrations and apply them as described in [Migrations](/docs/web/database/migrations). + + + After running your migrations, you may want to seed your database with initial data (such as demo users or organizations). You can do this by running the following command: + + ```bash + pnpm with-env pnpm turbo db:seed + ``` + + This will populate your Supabase database with some example data you can use to test your application. + + + + + ## Use Supabase Storage as S3-compatible storage + + TurboStarter's storage layer is designed to work seamlessly with **any S3-compatible provider**. In this section, we'll show how to use [Supabase Storage](/docs/web/storage/overview) as your application's file storage back-end. + + Supabase Storage provides a simple, S3-compatible API and is a great choice if you're already using Supabase for your database. + + ### Create a storage bucket + + 1. In the Supabase dashboard, go to **Storage → Buckets**. + 2. Click **Create bucket** (name it whatever you want, for example `avatars` or `uploads`). + 3. Adjust settings based on your needs (e.g. limit the maximum file size, specify the allowed file types, etc.) + + ![Create a new bucket](/images/docs/web/recipes/supabase/create-bucket.png) + + You can create multiple buckets (for documents, images, videos, etc.) if needed. + + ### Generate S3 access keys in Supabase dashboard + + 1. Go to **Storage → S3 → Access keys**. + 2. Click **New access key**. + 3. Give it a descriptive name and create the key. + 4. Copy the **Access key ID** and **Secret access key** to use in your application. + + ![Generate S3 access keys](/images/docs/web/recipes/supabase/s3-keys.png) + + ### Configure S3 environment variables for Supabase Storage + + In your weba application's `.env.local`, add (or update) the S3 configuration used by TurboStarter's storage layer: + + ```dotenv title=".env.local" + S3_REGION="us-east-1" + S3_BUCKET="avatars" + S3_ENDPOINT="https://[YOUR-PROJECT-REF].supabase.co/storage/v1/s3" + S3_ACCESS_KEY_ID="your-access-key-id" + S3_SECRET_ACCESS_KEY="your-secret-access-key" + ``` + + These variables integrate directly with the storage configuration described in: + + * [Storage overview](/docs/web/storage/overview) + * [Storage configuration](/docs/web/storage/configuration) + + Once set, existing TurboStarter file upload flows (e.g. user avatars, organization logos) will use Supabase Storage via presigned URLs. + + + + ## Run your API on Supabase Edge Functions + + As we're using a [Hono](https://hono.dev) as our API server, you can deploy it as a Supabase Edge Function so it runs close to your users. + + At a high level: + + 1. Install the [Supabase CLI](https://supabase.com/docs/guides/cli) and initialize a Supabase project locally with `supabase init`. + 2. Create a new [Edge Function](https://supabase.com/docs/guides/functions/quickstart) (for example `hono-backend`) with `supabase functions new hono-backend`. + 3. Inside the generated function (for example `supabase/functions/hono-backend/index.ts`), set up a basic Hono app and export it via `Deno.serve(app.fetch)`: + + ```ts + import { Hono } from "jsr:@hono/hono"; + + // change this to your function name + const functionName = "hono-backend"; + const app = new Hono().basePath(`/${functionName}`); + + app.get("/hello", (c) => c.text("Hello from hono-server!")); + + Deno.serve(app.fetch); + ``` + + 4. Run the function locally with `supabase start` and `supabase functions serve --no-verify-jwt`, then call it from your TurboStarter app using the local or deployed function URL. + 5. When you're ready, deploy the function with `supabase functions deploy` (or `supabase functions deploy hono-backend`) and manage it using the Supabase dashboard, as described in the [Supabase Edge Functions docs](https://supabase.com/docs/guides/functions). + + This is entirely optional, but it's a great fit for lightweight APIs, webhooks, and other serverless logic you want to run alongside your Supabase project. + + + + ## Explore additional Supabase features + + Supabase is a full Postgres development platform, so beyond the database and storage pieces wired up above you can gradually add more features as your app grows ([see the Supabase homepage](https://supabase.com/) for an overview). + + Some features that fit especially well with TurboStarter's design are: + + * [Realtime](https://supabase.com/docs/guides/realtime) - built on [Postgres replication](https://www.postgresql.org/docs/current/runtime-config-replication.html), so you can stream changes from your existing TurboStarter tables (inserts, updates, deletes) into live UIs without changing how you manage schema or RLS. You still define tables and policies via `@turbostarter/db`, and opt into Realtime on top. + * [Vector](https://supabase.com/docs/guides/vector) - powered by the [pgvector](https://github.com/pgvector/pgvector) extension and stored in regular Postgres tables, making it easy to integrate semantic search or AI features while keeping everything in the same migrations and Drizzle models you already use in TurboStarter. We're using it extensively in our dedicated [AI Kit](/ai). + * [Cron](https://supabase.com/docs/guides/functions/cron) - enables you to schedule background jobs and periodic tasks with [pg\_cron](https://github.com/citusdata/pg_cron). You can define cron jobs for things like scheduled database cleanups, sending emails, report generation, or any recurring logic, all managed alongside your TurboStarter app with full Postgres integration. + + Because these features are all layered on top of Postgres, you can introduce them incrementally and keep managing everything through your familiar workflow. + + + + ## Start the development server + + With the database and other services configured to use Supabase, you can start TurboStarter as usual from the monorepo root: + + ```bash + pnpm dev + ``` + + TurboStarter will now: + + * Use **Supabase Postgres** as your database through `DATABASE_URL` + * Use **Supabase Storage** as your file storage through the S3-compatible endpoint + * Leverage **Supabase Edge Functions** (for example, with Hono) for your serverless backend + + + +That's it! You can now start building your application with Supabase as your main provider. Explore the [Supabase documentation](https://supabase.com/docs) for more features and best practices. diff --git a/.context/turbostarter-framework-context/sections/mobile/stack.md b/.context/turbostarter-framework-context/sections/mobile/stack.md new file mode 100644 index 0000000..d63b438 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/stack.md @@ -0,0 +1,80 @@ +--- +title: Tech Stack +description: A detailed look at the technical details. +url: /docs/mobile/stack +--- + +# Tech Stack + +## Turborepo + +[Turborepo](https://turbo.build/) is a monorepo tool that helps you manage your project's dependencies and scripts. We chose a monorepo setup to make it easier to manage the structure of different features and enable code sharing between different packages. + +} /> + +## React Native + Expo + +[React Native](https://reactnative.dev/) is an open-source mobile application development framework created by Facebook. It is used to develop applications for Android and iOS by enabling developers to use [React](https://react.dev) along with native platform capabilities. + +> It's like Next.js for mobile development. + +[Expo](https://expo.dev/) is a framework and a platform built around React Native. It provides a set of tools and services that help you develop, build, deploy, and quickly iterate on iOS, Android, and web apps from the same JavaScript/TypeScript codebase. It's like Next.js for mobile development. + + + } /> + + } /> + + +## Tailwind CSS + +[Uniwind](https://uniwind.dev/) uses Tailwind CSS as scripting language to create a universal style system for React Native. It allows you to use Tailwind CSS classes in your React Native components, providing a familiar styling experience for web developers. We also use [React Native Reusables](https://github.com/mrzachnugent/react-native-reusables) for our headless components library with support of CLI to generate pre-designed components with a single command. + + + } /> + + } /> + + +## Hono & React Query + +[Hono](https://hono.dev) is a small, simple, and ultrafast web framework for the edge. It provides tools to help you build APIs and web applications faster. It includes an RPC client for making type-safe function calls from the frontend. We use Hono to build our serverless API endpoints. + +To make data fetching and caching from our API easy and reliable, we pair Hono with [React Query](https://tanstack.com/query/latest). It helps manage asynchronous data, caching, and state synchronization between the client and backend, delivering a fast and seamless UX. + + + } /> + + + } + /> + + +## Better Auth + +[Better Auth](https://www.better-auth.com) is a modern authentication library for fullstack applications. It provides ready-to-use snippets for features like email/password login, magic links, OAuth providers, and more. We use Better Auth to handle all authentication flows in our application. + +} /> + +## Drizzle + +[Drizzle](https://orm.drizzle.team/) is a super fast [ORM](https://orm.drizzle.team/docs/overview) (Object-Relational Mapping) tool for databases. It helps manage databases, generate TypeScript types from your schema, and run queries in a fully type-safe way. + +We use [PostgreSQL](https://www.postgresql.org) as our default database, but thanks to Drizzle's flexibility, you can easily switch to MySQL, SQLite or any [other supported database](https://orm.drizzle.team/docs/connect-overview) by updating a few configuration lines. + + + } /> + + } /> + + +## EAS (Expo Application Services) + +[EAS](https://expo.dev/eas) is a set of cloud services provided by Expo for React Native app development. It includes tools for building, submitting, and updating your app, as well as over-the-air updates and analytics. + +} /> diff --git a/.context/turbostarter-framework-context/sections/mobile/tests/e2e.md b/.context/turbostarter-framework-context/sections/mobile/tests/e2e.md new file mode 100644 index 0000000..d80a1a8 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/tests/e2e.md @@ -0,0 +1,15 @@ +--- +title: E2E tests +description: Simulate real user scenarios across the entire stack with automated end-to-end test tools and examples. +url: /docs/mobile/tests/e2e +--- + +# E2E tests + + + End-to-end (E2E) tests will be available soon, allowing you to automate testing of real user flows and interactions across your application. + + Stay tuned for updates as we roll out robust E2E testing resources and examples. + + [See roadmap](https://github.com/orgs/turbostarter/projects/1) + diff --git a/.context/turbostarter-framework-context/sections/mobile/tests/unit.md b/.context/turbostarter-framework-context/sections/mobile/tests/unit.md new file mode 100644 index 0000000..d3ac7d0 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/tests/unit.md @@ -0,0 +1,136 @@ +--- +title: Unit tests +description: Write and run fast unit tests for individual functions and components with instant feedback. +url: /docs/mobile/tests/unit +--- + +# Unit tests + +Unit tests are a type of automated test where individual units or components are tested. The "unit" in "unit test" refers to the smallest testable parts of an application. These tests are designed to verify that each unit of code performs as expected. + +TurboStarter uses [Vitest](https://vitest.dev) as the unit testing framework. It's a blazing-fast test runner built on top of [Vite](https://vitejs.dev), designed for modern JavaScript and TypeScript projects. + + + If you've used [Jest](https://jestjs.io) before, you already know Vitest - it shares the same API. But Vitest is built for speed: native TypeScript support without transpilation, parallel test execution, and a smart watch mode that only re-runs tests affected by your changes. + + It comes with everything you need out of the box - code coverage, snapshot testing, mocking, and a slick UI for debugging. Fast feedback, zero configuration. + + +## Why write unit tests? + +Unit tests give you **fast, focused feedback** on small pieces of your code - individual functions, hooks, or components. Instead of debugging an entire page or flow, you can verify just the logic you care about in isolation. + +They also act as **living documentation**: a good test tells you how a function is supposed to behave, which edge cases are important, and what assumptions the code makes. This makes it much easier to safely refactor or extend features later. + +In TurboStarter, unit tests are designed to be **cheap and quick to run**, so you can keep Vitest running in watch mode while you code. Every change you make is immediately checked, helping you catch regressions before they ever reach integration or end‑to‑end tests. + +## Configuration + +TurboStarter configures Vitest to be **as simple as possible**, while still taking advantage of [Turborepo's caching](https://turborepo.com/docs/crafting-your-repository/caching) and Vitest's [Test Projects](https://vitest.dev/guide/projects). + +```ts title="vitest.config.ts" +import { mergeConfig } from "vitest/config"; + +import baseConfig from "@turbostarter/vitest-config/base"; + +export default mergeConfig(baseConfig, { + test: { + /* your extended test configuration here */ + }, +}); +``` + +* **Per-package tests**: each package that has unit tests defines its own `test` script. This keeps the configuration close to the code and makes it easy to add tests to any workspace. +* **Turbo tasks for CI**: the root `test` task (`pnpm test`) uses `turbo run test` to execute all package-level test scripts with smart caching, which is ideal for CI pipelines where you want to avoid re-running unchanged tests. +* **Vitest Test Projects for local dev**: a root Vitest configuration uses [Test Projects](https://vitest.dev/guide/projects) to run all unit test suites from a single command, which is perfect for local development when you want fast feedback across the whole monorepo. + +This **hybrid setup** combines Turborepo and Vitest Projects in a way that fits TurboStarter's principles: cached, package-aware runs in CI, and a single, unified Vitest entry point for local development. + +You can read more about this setup in the official documentation guides listed below. + + + + + + + +## Running tests + +There are a few different ways to run unit tests, depending on what you're doing: + +* **CI / full test run** - at the root of the repo: + +```bash +pnpm test +``` + +This runs `turbo run test`, which executes all `test` scripts in packages that define them, with Turborepo handling caching so unchanged packages are skipped. This is what you should use in your CI/CD pipeline. + +* **One-off local run with Vitest Projects**: + +```bash +pnpm test:projects +``` + +This uses Vitest [Test Projects](https://vitest.dev/guide/projects) to run all configured unit test suites from a single command, which is great when you want to quickly validate the whole monorepo locally. + +* **Watch mode during development**: + +```bash +pnpm test:projects:watch +``` + +This starts Vitest in watch mode across all Test Projects. As you edit files, only the affected tests are re-run, giving you fast feedback while you work. + +## Code coverage + +Unit test coverage helps you understand **how much** of your code is being tested. While it can't guarantee bug-free code, it shines a light on untested paths that could hide issues or regressions. + +To generate a code coverage report for all unit tests, run: + +```bash +pnpm turbo test:coverage +``` + +This command runs the coverage task across all relevant packages (using Turborepo) and collects the results into a single coverage output. + +To open the coverage report in your browser: + +```bash +pnpm turbo test:coverage:view +``` + +This will build the HTML report and launch it using your default browser, so you can explore which files and branches are covered. + + + You can also store the generated coverage report as a [GitHub Actions artifact](https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts) during your CI/CD pipeline, just add the following steps to your workflow job: + + ```yaml title=".github/workflows/ci.yml" + # your workflow job configuration here + + - name: 📊 Generate coverage + run: pnpm turbo test:coverage + + - name: 🗃️ Archive coverage report + uses: actions/upload-artifact@v5 + with: + name: coverage-${{ github.sha }} + path: tooling/vitest/coverage/report + ``` + + This will generate a test coverage report and upload it as an artifact, so you can access it from GitHub Actions tab for later inspection. + + +A high coverage percentage means your tests execute most lines and branches - but the quality and relevance of your tests matter more than the raw number. Use coverage reports to spot gaps and guide improvements, not as the sole metric of test health. + +![Code coverage](/images/docs/code-coverage.png) + +## Best practices + +Unit tests should work **for you**, not the other way around. Focus on writing tests that make it easier to change code with confidence, not on satisfying arbitrary rules or reaching a magic number in a dashboard. + +Code coverage is a **useful metric**, but it **SHOULD NOT** be the goal. It's better to have a smaller set of high‑value tests that cover critical paths and edge cases than a huge suite of fragile tests that are hard to maintain. + +When in doubt, ask: *“Does this test give **me** confidence that I can change this code without breaking users?”* If the answer is no, refactor or remove it. + +Finally, keep unit tests focused on **small, isolated pieces of logic**. More advanced flows — like multi-step user journeys, cross-service interactions, or full-page behavior — are better covered by [end-to-end (E2E) tests](/docs/web/tests/e2e), where you can verify the system as a whole. diff --git a/.context/turbostarter-framework-context/sections/mobile/troubleshooting/installation.md b/.context/turbostarter-framework-context/sections/mobile/troubleshooting/installation.md new file mode 100644 index 0000000..4da14bc --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/troubleshooting/installation.md @@ -0,0 +1,89 @@ +--- +title: Installation +description: Find answers to common mobile installation issues. +url: /docs/mobile/troubleshooting/installation +--- + +# Installation + +## Cannot clone the repository + +Issues related to cloning the repository are usually related to a Git misconfiguration in your local machine. The commands displayed in this guide using SSH: these will work only if you have setup your SSH keys in Github. + +If you run into issues, [please make sure you follow this guide to set up your SSH key in Github.](https://docs.github.com/en/authentication/connecting-to-github-with-ssh) + +If this also fails, please use HTTPS instead. You will be able to see the commands in the repository's Github page under the "Clone" dropdown. + +Please also make sure that the account that accepted the invite to TurboStarter, and the locally connected account are the same. + +## Local database doesn't start + +If you cannot run the local database container, it's likely you have not started [Docker](https://docs.docker.com/get-docker/) locally. Our local database requires Docker to be installed and running. + +Please make sure you have installed Docker (or compatible software such as [Colima](https://github.com/abiosoft/colima), [Orbstack](https://github.com/orbstack/orbstack)) and that is running on your local machine. + +Also, make sure that you have enough [memory and CPU allocated](https://docs.docker.com/engine/containers/resource_constraints/) to your Docker instance. + +## I don't see my translations + +If you don't see your translations appearing in the application, there are a few common causes: + +1. Check that your translation `.json` files are properly formatted and located in the correct directory +2. Verify that the language codes in your configuration match your translation files +3. Enable debug mode (`debug: true`) in your i18next configuration to see detailed logs + +[Read more about configuration for translations](/docs/mobile/internationalization#configuration) + +## Expo cannot detect XCode + +If you get the following error: + +```bash +Expo cannot detect Xcode Xcode must be fully installed before you can continue +``` + +This is usually related to the Xcode CLI not being installed. You can fix this by running the following command: + +```bash +sudo xcode-select -s /Applications/Xcode.app/Contents/Developer +``` + +If you still face the issue, please make sure you have the latest version of Xcode installed. + +## "Module not found" error + +This issue is mostly related to either dependency installed in the wrong package or issues with the file system. + +The most common cause is incorrect dependency installation. Here's how to fix it: + +1. Clean the workspace: + + ```bash + pnpm clean + ``` + +2. Reinstall the dependencies: + ```bash + pnpm i + ``` + +If you're adding new dependencies, make sure to install them in the correct package: + +```bash +# For main app dependencies +pnpm install --filter mobile my-package + +# For a specific package +pnpm install --filter @turbostarter/ui my-package +``` + +If the issue persists, please check the file system for any issues. + +### Windows OneDrive + +OneDrive can cause file system issues with Node.js projects due to its file syncing behavior. If you're using Windows with OneDrive, you have two options to resolve this: + +1. Move your project to a location outside of OneDrive-synced folders (recommended) +2. Disable OneDrive sync specifically for your development folder + +This prevents file watching and symlink issues that can occur when OneDrive tries to sync Node.js project files. diff --git a/.context/turbostarter-framework-context/sections/mobile/troubleshooting/publishing.md b/.context/turbostarter-framework-context/sections/mobile/troubleshooting/publishing.md new file mode 100644 index 0000000..75e4ede --- /dev/null +++ b/.context/turbostarter-framework-context/sections/mobile/troubleshooting/publishing.md @@ -0,0 +1,75 @@ +--- +title: Publishing +description: Find answers to common mobile publishing issues. +url: /docs/mobile/troubleshooting/publishing +--- + +# Publishing + +## My app submission was rejected + +If your app submission was rejected, you probably got an email with the reason. You'll need to fix the issues and upload a new build of your app to the store and send it for review again. + +Make sure to follow the [guidelines](/docs/mobile/marketing) when submitting your app to ensure that everything is setup correctly. + +## App Store screenshots don't match requirements + +If your app submission was rejected due to screenshot issues, make sure: + +1. Screenshots match the required dimensions for each device +2. Screenshots accurately represent your app's functionality +3. You have provided screenshots for all required device sizes +4. Screenshots don't contain device frames unless they match Apple's requirements + +[See Apple's screenshot specifications](https://developer.apple.com/help/app-store-connect/reference/screenshot-specifications/) + +## Version number conflicts + +If you get version number conflicts when submitting: + +1. Ensure your `app.json` version matches what's in the store +2. Increment the version number appropriately: + ```bash + "version": "1.0.1", + "android.versionCode": 2, + "ios.buildNumber": "2" + ``` +3. Make sure both stores have unique version numbers + +## Missing or incorrect environment variables + +If your build succeeds but the binary is misconfigured (e.g., API URL shows as `undefined`, Sentry auth fails, or `app.config.*` settings don’t apply), verify your EAS environment variables: + +1. Define variables on EAS and assign them to the correct environment (`development`, `preview`, `production`). +2. For values used in app code, prefix with `EXPO_PUBLIC_` and read via `process.env.EXPO_PUBLIC_...`. +3. For config-time values (bundle identifiers, file paths), read `process.env.VARNAME` from your `app.config.*`. +4. Explicitly set `environment` in `eas.json` build profiles, or pass `--environment` to `eas update` so updates use the same variables as builds. +5. For local development, pull variables into a `.env` file: + ```bash + eas env:pull --environment development + ``` +6. Use secret file variables (e.g., `GOOGLE_SERVICES_JSON`) and reference them in `app.config.*`. +7. Keep `.env` out of git; cloud builds don’t rely on your local `.env`. + +See: [Environment variables in EAS](https://docs.expo.dev/eas/environment-variables/). + +## My app crashes on production build + +If the app works in development but crashes in a production build, check these common causes: + +1. **Missing or incorrect environment variables at build time**. EAS cloud jobs don’t use your local `.env` by default. Ensure variables exist on EAS, are assigned to the correct environment, and use `EXPO_PUBLIC_` for values read in app code. See: [Environment variables in EAS](https://docs.expo.dev/eas/environment-variables/). +2. **Missing native config files**. Provide `google-services.json` / `GoogleService-Info.plist` via secret file variables (e.g., `GOOGLE_SERVICES_JSON`) and reference them in `app.config.*`. +3. **Production-only code paths**. Guard dev-only code with `__DEV__`, avoid importing dev tools in production, and ensure feature flags don’t access undefined values. +4. **Misconfigured native modules or plugins**. Verify required plugins/babel config are present and rebuild after cache clears. + +Try this: + +1. Run the app with a production JS bundle locally to surface minification issues: + ```bash + npx expo start --no-dev --minify + ``` +2. Inspect device logs when the crash occurs (Android: `adb logcat`, iOS: Console.app or Xcode Devices). +3. Rebuild with a clean cache if needed: + ```bash + eas build --clear-cache + ``` diff --git a/.context/turbostarter-framework-context/sections/web/admin/overview.md b/.context/turbostarter-framework-context/sections/web/admin/overview.md new file mode 100644 index 0000000..9ae0576 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/admin/overview.md @@ -0,0 +1,67 @@ +--- +title: Overview +description: Get started with the admin dashboard in TurboStarter. +url: /docs/web/admin/overview +--- + +# Overview + +TurboStarter ships with a fully functional admin dashboard - it's a comprehensive tool for managing your application and users from one central place. + +The panel is designed to be intuitive and easy to use, while being customizable and scalable at the same time. You can access it at [/admin](http://localhost:3000/admin). + +![Admin Dashboard](/images/docs/web/admin/home.png) + +## Roles and permissions + +With the initial configuration, your app has two roles available to users: `user` and `admin`. By default, all users are created with the `user` role. + +To access the admin dashboard, a user must have the `admin` permission. + +```ts +const UserRole = { + USER: "user", + ADMIN: "admin", +} as const; +``` + +You can, of course, define more roles and assign granular permissions, but we recommend keeping the number of roles to a minimum. + +## Making a user an admin + +To promote a user to the admin role, use your database provider's UI or leverage our built-in [Studio](/docs/web/database/overview#studio). After you find the user you want to promote, change their role from `user` to `admin`. + +**Ensure the user you are promoting truly requires admin privileges, as they will gain access to all resources and permissions.** + + + To determine whether a user is eligible for the `admin` role, review the following recommendations before promoting the user: + + * The user's email is verified + * Two-factor authentication (2FA) is enabled + * The user is **not** banned or reported + + + + By default, when you [run services](/docs/web/installation/commands#setting-up-services) for the first time, your database is [seeded](/docs/web/installation/commands#seeding-database) with example data. This includes an admin user with test credentials that you can use to test admin functionality locally. + + ```json + { + "email": "me+admin@turbostarter.dev", + "password": "Pa$$w0rd" + } + ``` + + You can modify these by setting the `SEED_EMAIL` and `SEED_PASSWORD` environment variables in the `.env.local` file and running the seed process again. + + **This flow is for local testing purposes only. Do not use it in production.** + + +## Dashboard + +The admin dashboard is your **central place** to manage your application. It includes management tools for each resource you have defined. + +Users with the `admin` permission will see an additional dropdown item in the navigation menu, allowing them to access the admin dashboard. + +![Admin access through the navigation menu](/images/docs/web/admin/user-navigation.png) + +Explore each section of the page below to familiarize yourself with the available tools and options. diff --git a/.context/turbostarter-framework-context/sections/web/admin/ui.md b/.context/turbostarter-framework-context/sections/web/admin/ui.md new file mode 100644 index 0000000..9687281 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/admin/ui.md @@ -0,0 +1,115 @@ +--- +title: Super Admin UI +description: Get familiar with the Super Admin dashboard and start managing your application. +url: /docs/web/admin/ui +--- + +# Super Admin UI + +When you open [/admin](http://localhost:3000/admin), you will see the homepage of the admin dashboard. It includes some quick actions and a summary of the resources you have in your application. Feel free to customize it to your needs. + +To simplify navigation, we also shipped a sidebar that you can use to navigate between different sections and access all admin capabilities. + +![Super Admin UI](/images/docs/web/admin/home.png) + +Check below for more details about each section. + +## Users + +Central place to manage your users. You can see the list of users, search and filter them e.g. by role, 2FA, banned state, and created date. + +Use it to quickly find users that you need to manage or to see how your SaaS is performing. + +![Users](/images/docs/web/admin/users.png) + +When you click on a user, you will see the user details. You can edit the user's name and role, view the user's 2FA status, and see the user's created/updated timestamps. + +You can also see and manage the resources related to this specific user like user's connected accounts/providers, subscriptions, memberships, etc. + +![User](/images/docs/web/admin/user.png) + +Beyond simply viewing user information, the admin dashboard enables you to perform a variety of essential user management actions, including: + +* **Impersonate the user**: Temporarily log in as the selected user to troubleshoot their experience, verify permissions, or offer assistance directly from their perspective. +* **Ban or unban the user**: Restrict access to your application by banning users who violate terms of service, or lift restrictions when appropriate by unbanning them. +* **Delete the user**: Permanently remove a user and any associated data from your system when necessary, such as for GDPR compliance or at user request. + +These administrative actions help you maintain a secure, compliant, and user-friendly environment for your SaaS platform. + +## Organizations + +See how your multi-tenancy is performing in an elegant way presented as a data table. You can search and filter organizations by name, slug, member count and many more. + +![Organizations](/images/docs/web/admin/organizations.png) + +In the single organization view, you can get an overview of the specified organization, e.g see its members or invitations that are associated with it. + +![Organization](/images/docs/web/admin/organization.png) + +Here are some example actions you can perform when managing an organization: + +* **Edit organization details**: Change the organization name, slug, or other profile information. +* **Invite or remove members**: Add new users to an organization or revoke access from existing members. +* **Change member roles**: Promote a member to an admin or downgrade their access. +* **View and manage invitations**: See pending invites and revoke them if needed. +* **Delete organization**: Remove an organization and all its related data (action usually restricted to super admins). +* **Impersonate organization admin**: Temporarily assume the perspective of an organization's admin for troubleshooting. +* **Audit activity**: View a history of actions taken within the organization for security and compliance. + +These actions help you maintain control over multi-tenant environments and ensure that your SaaS remains secure and organized. + +## Customers + +Manage your customers and their subscriptions. Use search, filters, and sorting to quickly find the right record and understand billing state at a glance. + +![Customers](/images/docs/web/admin/customers.png) + +A few example actions you can perform when managing a customer: + +* **Open a customer** to view subscription details and billing history. +* **Change subscription plan** or move a customer to a different tier. +* **Start or extend a trial**, or **cancel a subscription** when needed. +* **Update billing details** like billing email and tax information. +* **Delete customer** to remove them and their billing profile (restricted action). + +## Add your own resources + +It’s your admin panel at the end of the day - extend it with any domain‑specific resources your product needs. The UI ships with reusable table, filter, form, and layout primitives so you can compose new sections quickly. + +To make CRUD panels fast to build, we also provide dedicated hooks, UI components, and API helpers that handle the boring plumbing - data fetching, pagination, sorting, filters, and mutations — so you can focus on your domain logic instead of boilerplate. + + + + ### Start from an example + + Duplicate an existing resource (like `Users` or `Organizations`) as a baseline and adjust the schema/columns to your needs. + + + + ### Build the list view + + Compose a data table with columns, sorting, full‑text search, and filters using the shipped primitives. + + Leverage the dedicated hooks, UI components, and API helpers to handle fetching, pagination, sorting, filters, and mutations with minimal boilerplate. + + + + ### Add a details view + + Create a single‑resource page and, if helpful, add tabs for related entities (e.g., memberships, invoices) using the same building blocks. + + + + ### Wire up navigation + + Register your route in the admin sidebar so the new resource appears alongside the built‑ins. + + + + ### Secure with permissions + + Protect access using your RBAC rules and feature flags to control who can view or manage the resource. + + + +Et voilà! You now have a new resource in your admin panel 🥳 diff --git a/.context/turbostarter-framework-context/sections/web/ai/configuration.md b/.context/turbostarter-framework-context/sections/web/ai/configuration.md new file mode 100644 index 0000000..5913fe7 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/ai/configuration.md @@ -0,0 +1,94 @@ +--- +title: Configuration +description: Configure AI integration in your TurboStarter project. +url: /docs/web/ai/configuration +--- + +# Configuration + +To ensure scalability and avoid security vulnerabilities, AI requests are proxied by our [Hono backend](/docs/web/api/overview). This means you need to set up AI integration on both the client and server side. + + + We want to avoid exposing API keys directly to the browser, as this could lead to abuse of your key and generate unnecessary costs. + + +In this section, we'll explore the configuration for both sides to give you a smooth start. + +## Server-side + +On the backend, you need to set up two things: environment variables to configure the provider and the procedure to pass responses to the client. Let's go through it! + +### Environment variables + +You need to set the environment variables that correspond to the AI provider you want to use. + +For example, for the OpenAI provider, you would need to set the following environment variables: + +```dotenv +OPENAI_API_KEY= +``` + +However, if you want to use the Anthropic provider, you would need to set these environment variables: + +```dotenv +ANTHROPIC_API_KEY= +``` + +You can find the list of all available providers in the [official documentation](https://sdk.vercel.ai/providers/ai-sdk-providers), along with the required variables that need to be set to ensure the integration works correctly. + +### API endpoint + +As we're proxying the requests, we need to register an [API endpoint](/docs/web/api/new-endpoint) that will be used to pass the responses to the client. + +The steps will be the same as we described in the [API](/docs/web/api/new-endpoint) section. An example implementation could look like this: + +```ts title="ai/router.ts" +export const aiRouter = new Hono().post("/chat", async (c) => + streamText({ + model: openai.responses("gpt-5"), + messages: convertToModelMessages((await c.req.json()).messages), + }).toUIMessageStreamResponse(), +); +``` + +As you can see, we're defining which provider and specific model we want to use here. + +We're also using [Streams API](https://developer.mozilla.org/en-US/docs/Web/API/Streams_API/Concepts), which allows us to pass the result to the user as soon as the model starts generating it, without needing to wait for the full response to be completed. This gives the user a sense of immediacy and makes the conversation more interactive. + +## Client-side + +To consume the server response, we can leverage the ready-to-use hooks provided by the [Vercel AI SDK](https://sdk.vercel.ai/docs/ai-sdk-ui/chatbot), such as the `useChat` hook: + +```tsx title="page.tsx" +import { useChat } from "@ai-sdk/react"; +import { DefaultChatTransport } from "ai"; + +const AI = () => { + const { messages } = useChat({ + transport: new DefaultChatTransport({ + api: "/api/ai/chat", + }), + }); + + return ( +
+ {messages.map((message) => ( +
+ {message.parts.map((part, i) => { + switch (part.type) { + case "text": + return
{part.text}
; + } + })} +
+ ))} +
+ ); +}; + +export default AI; +``` + +By leveraging this integration, we can easily manage the state of the AI request and update the UI as soon as the response is ready. + +TurboStarter ships with a ready-to-use implementation of AI chat, allowing you to see this solution in action. Feel free to reuse or modify it according to your needs. diff --git a/.context/turbostarter-framework-context/sections/web/ai/overview.md b/.context/turbostarter-framework-context/sections/web/ai/overview.md new file mode 100644 index 0000000..d6924dd --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/ai/overview.md @@ -0,0 +1,45 @@ +--- +title: Overview +description: Get started with AI integration in your TurboStarter project. +url: /docs/web/ai/overview +--- + +# Overview + +For AI integration, TurboStarter leverages the [Vercel AI SDK](https://sdk.vercel.ai/docs/introduction), which provides a comprehensive set of tools and utilities to help you build AI applications more easily and efficiently. + + + It's a simple yet powerful library that exposes a unified API for all major AI providers. + + This allows you to build your AI application without worrying about the intricacies of each underlying provider's API. + + +You can learn more about the `ai` package in the [official documentation](https://sdk.vercel.ai/docs/introduction). + +## Features + +The starter comes with the most common AI features built-in, such as: + +* **Chat**: Build chat applications with ease. +* **Streaming responses**: Stream responses from your AI provider in real-time. +* **Image generation**: Generate images using AI technology. +* **Embeddings**: Generate embeddings for your data. +* **Vector stores**: Store and query your embeddings efficiently. + +You can easily compose your application using these building blocks or extend them to suit your specific needs. + +## Providers + +**TurboStarter relies on the AI SDK to provide support for various AI providers.** + +This means you can easily switch between different AI providers without changing your code, as long as they are supported by the `ai` package. + +You can find the list of supported providers in the [official documentation](https://sdk.vercel.ai/providers/ai-sdk-providers). + + + There is also the possibility to add your own custom provider. It just needs to implement the common interface and provide all the necessary methods. + + Read more about this in the [official guide](https://sdk.vercel.ai/providers/community-providers/custom-providers). + + +The configuration for each provider is straightforward and simple. We'll explore this in more detail in the [Configuration](/docs/web/ai/configuration) section. diff --git a/.context/turbostarter-framework-context/sections/web/analytics/configuration.md b/.context/turbostarter-framework-context/sections/web/analytics/configuration.md new file mode 100644 index 0000000..beeeeba --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/analytics/configuration.md @@ -0,0 +1,409 @@ +--- +title: Configuration +description: Learn how to configure web analytics in TurboStarter. +url: /docs/web/analytics/configuration +--- + +# Configuration + +The `@turbostarter/analytics-web` package offers a streamlined and flexible approach to tracking events in your TurboStarter web app using various analytics providers. It abstracts the complexities of different analytics services and provides a consistent interface for event tracking. + +In this section, we'll guide you through the configuration process for each supported provider. + +Note that the configuration is validated against a schema, so you'll see error messages in the console if anything is misconfigured. + +## Providers + +TurboStarter supports multiple analytics providers, each with its own unique configuration. Below, you'll find detailed information on how to set up and use each supported provider. Choose the one that best suits your needs and follow the instructions in the respective accordion section. + + + + To use Vercel Analytics as your provider, you need to [create a Vercel account](https://vercel.com/) and [set up a project](https://vercel.com/docs/projects/overview). + + Next, enable analytics in your Vercel project settings: + + 1. Navigate to the [Vercel dashboard](https://vercel.com/dashboard). + 2. Select your project. + 3. Go to the *Analytics* section. + 4. Click *Enable* in the dialog. + + + Enabling Web Analytics will add new routes (scoped at `/_vercel/insights/*`) after your next deployment. + + + Also, make sure to activate the Vercel provider as your analytics provider by updating the exports in: + + + + ```ts + // [!code word:vercel] + export * from "./vercel"; + ``` + + + + ```ts + // [!code word:vercel] + export * from "./vercel/server"; + ``` + + + + ```ts + // [!code word:vercel] + export * from "./vercel/env"; + ``` + + + + To customize the provider, you can find its definition in `packages/analytics/web/src/providers/vercel` directory. + + For more information, please refer to the [Vercel Analytics documentation](https://vercel.com/docs/analytics/overview). + + ![Vercel Analytics dashboard](/images/docs/web/analytics/vercel.avif) + + + + To use Google Analytics as your analytics provider, you need to [create a Google Analytics account](https://analytics.google.com/) and [set up a property](https://support.google.com/analytics/answer/9304153). + + Next, add a data stream in your Google Analytics account settings: + + 1. Navigate to [Google Analytics](https://analytics.google.com/). + 2. In the *Admin* section, under *Data collection and modification*, click on *Data Streams*. + 3. Click *Add stream*. + 4. Select *Web* as the platform. + 5. Enter the required details for the stream (at minimum, provide a name and website URL). + 6. Click *Create stream*. + + After creating the stream, you'll need two pieces of information: + + 1. Your [Measurement ID](https://support.google.com/analytics/answer/12270356) (it should look like `G-XXXXXXXXXX`): + + ![Google Analytics Measurement ID](/images/docs/web/analytics/google/id.png) + + 2. Your [Measurement Protocol API secret](https://support.google.com/analytics/answer/9814495): + + ![Google Analytics Measurement Protocol API secret](/images/docs/web/analytics/google/api-secret.png) + + Set these values in your `.env.local` file in the `apps/web` directory and in your deployment environment: + + ```dotenv + NEXT_PUBLIC_ANALYTICS_GOOGLE_MEASUREMENT_ID="your-measurement-id" + GOOGLE_ANALYTICS_SECRET="your-measurement-protocol-api-secret" + ``` + + Also, make sure to activate the Google Analytics provider as your analytics provider by updating the exports in: + + + + ```ts + // [!code word:google-analytics] + export * from "./google-analytics"; + ``` + + + + ```ts + // [!code word:google-analytics] + export * from "./google-analytics/server"; + ``` + + + + ```ts + // [!code word:google-analytics] + export * from "./google-analytics/env"; + ``` + + + + To customize the provider, you can find its definition in `packages/analytics/web/src/providers/google-analytics` directory. + + For more information, please refer to the [Google Analytics documentation](https://developers.google.com/analytics). + + ![Google Analytics dashboard](/images/docs/web/analytics/google/dashboard.jpg) + + + + + PostHog is also one of pre-configured providers for [monitoring](/docs/web/monitoring/overview) in TurboStarter. You can learn more about it [here](/docs/web/monitoring/posthog). + + + To use PostHog as your analytics provider, you need to configure a PostHog instance. You can obtain the [Cloud](https://app.posthog.com/signup) instance by [creating an account](https://app.posthog.com/signup) or [self-host](https://posthog.com/docs/self-host) it. + + Then, create a project and, based on your [project settings](https://app.posthog.com/project/settings), fill the following environment variables in your `.env.local` file in `apps/web` directory and your deployment environment: + + ```dotenv + NEXT_PUBLIC_POSTHOG_KEY="your-posthog-api-key" + NEXT_PUBLIC_POSTHOG_HOST="your-posthog-instance-host" + ``` + + Also, make sure to activate the PostHog provider as your analytics provider by updating the exports in: + + + + ```ts + // [!code word:posthog] + export * from "./posthog"; + ``` + + + + ```ts + // [!code word:posthog] + export * from "./posthog/server"; + ``` + + + + ```ts + // [!code word:posthog] + export * from "./posthog/env"; + ``` + + + + To customize the provider, you can find its definition in `packages/analytics/web/src/providers/posthog` directory. + + For more information, please refer to the [PostHog documentation](https://posthog.com/docs). + + ![PostHog dashboard](/images/docs/web/analytics/posthog.png) + + + + To use Mixpanel as your analytics provider, you need to [create an account](https://mixpanel.com/) and [obtain your project token](https://help.mixpanel.com/hc/en-us/articles/115004502806-Find-Project-Token). + + Then, set it as an environment variable in your `.env.local` file in the `apps/web` directory and your deployment environment: + + ```dotenv + NEXT_PUBLIC_MIXPANEL_TOKEN="your-project-token" + ``` + + Also, make sure to activate the Mixpanel provider as your analytics provider by updating the exports in: + + + + ```ts + // [!code word:mixpanel] + export * from "./mixpanel"; + ``` + + + + ```ts + // [!code word:mixpanel] + export * from "./mixpanel/server"; + ``` + + + + ```ts + // [!code word:mixpanel] + export * from "./mixpanel/env"; + ``` + + + + To customize the provider, you can find its definition in `packages/analytics/web/src/providers/mixpanel` directory. + + For more information, please refer to the [Mixpanel documentation](https://docs.mixpanel.com/). + + ![Mixpanel dashboard](/images/docs/web/analytics/mixpanel.png) + + + + To use Plausible as your analytics provider, you need to [create an account](https://plausible.io/) and [set up a website](https://plausible.io/docs/add-website). + + Then, set your domain and host in your `.env.local` file in the `apps/web` directory and your deployment environment: + + ```dotenv + NEXT_PUBLIC_PLAUSIBLE_DOMAIN="your-website-domain.com" + NEXT_PUBLIC_PLAUSIBLE_HOST="https://plausible.io" + ``` + + Also, make sure to activate the Plausible provider as your analytics provider by updating the exports in: + + + + ```ts + // [!code word:plausible] + export * from "./plausible"; + ``` + + + + ```ts + // [!code word:plausible] + export * from "./plausible/server"; + ``` + + + + ```ts + // [!code word:plausible] + export * from "./plausible/env"; + ``` + + + + To customize the provider, you can find its definition in `packages/analytics/web/src/providers/plausible` directory. + + For more information, please refer to the [Plausible documentation](https://plausible.io/docs). + + ![Plausible dashboard](/images/docs/web/analytics/plausible.png) + + + + To use Umami as your analytics provider, you need to [set up Umami](https://umami.is/docs/getting-started) either by using their [cloud service](https://cloud.umami.is/) or [self-hosting](https://umami.is/docs/install). + + Then, set your website ID and host in your `.env.local` file in the `apps/web` directory and your deployment environment: + + ```dotenv + NEXT_PUBLIC_UMAMI_WEBSITE_ID="your-website-id" + NEXT_PUBLIC_UMAMI_HOST="https://your-umami-instance.com" + UMAMI_API_HOST="https://your-umami-instance.com" + UMAMI_API_KEY="your-api-key" + ``` + + Also, make sure to activate the Umami provider as your analytics provider by updating the exports in: + + + + ```ts + // [!code word:umami] + export * from "./umami"; + ``` + + + + ```ts + // [!code word:umami] + export * from "./umami/server"; + ``` + + + + ```ts + // [!code word:umami] + export * from "./umami/env"; + ``` + + + + To customize the provider, you can find its definition in `packages/analytics/web/src/providers/umami` directory. + + For more information, please refer to the [Umami documentation](https://umami.is/docs). + + ![Umami dashboard](/images/docs/web/analytics/umami.jpg) + + + + To use Open Panel as your analytics provider, you need to [create an account](https://openpanel.dev/) and [set up a client for your project](https://docs.openpanel.dev/docs). + + Then, you would need to set your client ID and secret in your `.env.local` file in `apps/web` directory and your deployment environment: + + ```dotenv + NEXT_PUBLIC_OPEN_PANEL_CLIENT_ID="your-client-id" + OPEN_PANEL_CLIENT_SECRET="your-client-secret" + ``` + + Also, make sure to activate the Open Panel provider as your analytics provider by updating the exports in: + + + + ```ts + // [!code word:open-panel] + export * from "./open-panel"; + ``` + + + + ```ts + // [!code word:open-panel] + export * from "./open-panel/server"; + ``` + + + + ```ts + // [!code word:open-panel] + export * from "./open-panel/env"; + ``` + + + + To customize the provider, you can find its definition in `packages/analytics/web/src/providers/open-panel` directory. + + For more information, please refer to the [Open Panel documentation](https://docs.openpanel.dev/). + + ![Open Panel dashboard](/images/docs/web/analytics/open-panel.webp) + + + + To use Vemetric as your analytics provider, you need to [create an account](https://vemetric.com/) and [obtain your project token](https://vemetric.com/docs/). + + Then, set it as an environment variable in your `.env.local` file in the `apps/web` directory and your deployment environment: + + ```dotenv + NEXT_PUBLIC_VEMETRIC_PROJECT_TOKEN="your-project-token" + ``` + + Also, make sure to activate the Vemetric provider as your analytics provider by updating the exports in: + + + + ```ts + // [!code word:vemetric] + export * from "./vemetric"; + ``` + + + + ```ts + // [!code word:vemetric] + export * from "./vemetric/server"; + ``` + + + + ```ts + // [!code word:vemetric] + export * from "./vemetric/env"; + ``` + + + + To customize the provider, you can find its definition in `packages/analytics/web/src/providers/vemetric` directory. + + For more information, please refer to the [Vemetric documentation](https://vemetric.com/docs/). + + ![Vemetric dashboard](/images/docs/web/analytics/vemetric.webp) + + + +## Client-side context + +To enable tracking events, capturing page views and other analytics features **on the client-side**, you need to wrap your app with the `Provider` component that's implemented by every provider and available through the `@turbostarter/analytics-web` package: + +```tsx title="providers.tsx" +// [!code word:AnalyticsProvider] +import { memo } from "react"; + +import { Provider as AnalyticsProvider } from "@turbostarter/analytics-web"; + +interface ProvidersProps { + readonly children: React.ReactNode; +} + +export const Providers = memo(({ children }) => { + return ( + + {children} + + ); +}); + +Providers.displayName = "Providers"; +``` + +By implementing this setup, you ensure that all analytics events are properly tracked from your client-side code. This configuration allows you to safely utilize the [Analytics API](/docs/web/analytics/tracking) within your client components, enabling comprehensive event tracking and data collection. diff --git a/.context/turbostarter-framework-context/sections/web/analytics/overview.md b/.context/turbostarter-framework-context/sections/web/analytics/overview.md new file mode 100644 index 0000000..f8d8661 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/analytics/overview.md @@ -0,0 +1,35 @@ +--- +title: Overview +description: Get started with web analytics in TurboStarter. +url: /docs/web/analytics/overview +--- + +# Overview + +TurboStarter comes with built-in analytics support for multiple providers as well as a unified API for tracking events. This API enables you to easily and consistently track user behavior and app usage across your SaaS application. + +## Providers + +The starter implements multiple providers for managing analytics. To learn more about each provider and how to configure them, see their respective sections: + + + + + + + + + + + + + + + + + + + +All configuration and setup is built-in with a unified API, allowing you to switch between providers by simply changing the exports. You can even introduce your own provider without breaking any tracking-related logic. + +In the following sections, we'll cover how to set up each provider and how to track events in your application. diff --git a/.context/turbostarter-framework-context/sections/web/analytics/tracking.md b/.context/turbostarter-framework-context/sections/web/analytics/tracking.md new file mode 100644 index 0000000..89c8b29 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/analytics/tracking.md @@ -0,0 +1,139 @@ +--- +title: Tracking events +description: Learn how to track events in your TurboStarter web app. +url: /docs/web/analytics/tracking +--- + +# Tracking events + +The implementation strategy for each analytics provider varies depending on whether it's designed for client-side or server-side use. We'll explore both approaches, as they are crucial for ensuring accurate and comprehensive analytics data in your web SaaS application. + +## Client-side tracking + +The client strategy for tracking events, which every provider must implement, is straightforward: + +```ts +export type AllowedPropertyValues = string | number | boolean; + +type TrackFunction = ( + event: string, + data?: Record, +) => void; + +export interface AnalyticsProviderClientStrategy { + Provider: ({ children }: { children: React.ReactNode }) => React.ReactNode; + track: TrackFunction; +} +``` + + + You don't need to worry much about this implementation, as all the providers are already configured for you. However, it's useful to be aware of this structure if you plan to add your own custom provider. + + +As shown above, each provider must supply two key elements: + +1. `Provider` - a component that [wraps your app](/docs/web/analytics/configuration#client-side-context). +2. `track` - a function responsible for sending event data to the provider. + +To track an event, you simply need to invoke the `track` method, passing the event name and an optional data object: + +```tsx +import { track } from "@turbostarter/analytics-web"; + +export const MyComponent = () => { + return ( + + ); +}; +``` + +## Identifying users + +Linking events to specific users enables you to build a full picture of how they're using your product across different sessions, devices, and platforms. + +For identification purposes, the client strategy can also expose `identify` and `reset` methods. They are optional and only needed if you want to identify users in your app and associate their actions with a specific user ID. + +Not all analytics providers support user identification (for example, [Vercel Analytics](/docs/web/analytics/configuration#vercel) and [Plausible](/docs/web/analytics/configuration#plausible)), so make sure your chosen provider exposes these methods before using them. + +```ts +type IdentifyFunction = ( + userId: string, + traits?: Record, +) => void; + +export interface AnalyticsProviderClientStrategy { + identify: IdentifyFunction; + reset: () => void; +} +``` + +To identify users on the client, call the `identify` function, passing the user's ID and an optional traits object: + +```tsx +import { identify } from "@turbostarter/analytics-web"; + +identify("user-123", { name: "John Doe" }); +``` + +This will associate all future events with the user's ID, allowing you to track user behavior and gain valuable insights into your application's usage patterns. + + + The `identify` method is configured out-of-the-box to react to changes in the user's authentication state. + + When the user is authenticated, the `identify` method will be called with the user's ID and traits. When the user is logged out, the `reset` method will be called to clear the existing user identification. + + +## Server-side tracking + +The server strategy for tracking events that every provider has to implement is even simpler: + +```ts +export interface AnalyticsProviderServerStrategy { + track: TrackFunction; +} +``` + + + You don't need to worry much about this implementation, as all the providers are already configured for you. However, it's useful to be aware of this structure if you plan to add your own custom provider. + + +This server-side strategy allows you to track events outside of the browser environment, which is particularly useful for scenarios involving server actions or React Server Components. + +To track an event on the server side, simply call the `track` method, providing the event name and an optional data object: + +```tsx +// [!code word:server] +import { track } from "@turbostarter/analytics-web/server"; + +track("button.click", { + country: "US", + region: "California", +}); +``` + + + Make sure to use the correct import for the `track` function. We're using the same name for both client and server tracking, but they are different functions. For server-side, just add `/server` to the import path (`@turbostarter/analytics-web/server`). + + + + ```tsx + import { track } from "@turbostarter/analytics-web"; + ``` + + + + ```tsx + // [!code word:server] + import { track } from "@turbostarter/analytics-web/server"; + ``` + + + + + + On the server, there are no dedicated identification helpers like `identify` or `reset`. Most providers that support user-level tracking expect you to pass an identifier or traits directly within the `track` call (for example, as a `userId` or similar property), so make sure to check your specific provider's documentation for the recommended way to include user information. + + +Congratulations! You've now mastered event tracking in your TurboStarter web app. With this knowledge, you're well-equipped to analyze user behaviors and gain valuable insights into your application's usage patterns. Happy analyzing! 📊 diff --git a/.context/turbostarter-framework-context/sections/web/api/client.md b/.context/turbostarter-framework-context/sections/web/api/client.md new file mode 100644 index 0000000..530870f --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/api/client.md @@ -0,0 +1,163 @@ +--- +title: Using API client +description: How to use API client to interact with the API. +url: /docs/web/api/client +--- + +# Using API client + +In Next.js, you can access the API client in two ways: + +* **server-side**: in server components and API routes +* **client-side**: in client components + +When you create a new page and want to fetch data, you have flexibility in where to make the API calls. Server Components are great for initial data loading since the fetching happens during server-side rendering, eliminating an extra client-server round trip. The data is then efficiently streamed to the client. + +By default in Next.js, every component is a Server Component. You can opt into client-side rendering by adding the `use client` directive at the top of a component file. Client Components are useful when you need interactive features or want to fetch data based on user interactions. While they're initially server-rendered, they're also hydrated and rendered on the client, allowing you to make API calls directly from the browser. + +Let's explore both approaches to understand their differences and use cases. + +## Server-side + +We're creating a server-side API client inside `apps/web/src/lib/api/server.ts` file. The client automatically handles passing authentication headers from the user's session to secure API endpoints. + +It's pre-configured with all the necessary setup, so you can start using it right away without any additional configuration. + +Then, there is nothing simpler than calling the API from your server component: + +```tsx title="page.tsx" +import { api } from "~/lib/api/server"; + +export default async function MyServerComponent() { + const response = await api.posts.$get(); + const posts = await response.json(); + + /* do something with the data... */ + return
{JSON.stringify(posts)}
; +} +``` + + + +## Client-side + +We're creating a separate client-side API client in `apps/web/src/lib/api/client.tsx` file. It's a simple wrapper around the [@tanstack/react-query](https://tanstack.com/query/latest/docs/framework/react/overview) that fetches or mutates data from the API. + +It also requires wrapping your app in a `QueryClientProvider` component to provide the query client to the rest of the app: + +```tsx title="layout.tsx" +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + {children} + + + ); +} +``` + +Of course, it's all already configured for you, so you just need to start using `api` in your client components: + +```tsx title="page.tsx" +"use client"; + +import { api } from "~/lib/api/client"; + +export default function MyClientComponent() { + const { data: posts, isLoading } = useQuery({ + queryKey: ["posts"], + queryFn: async () => { + const response = await api.posts.$get(); + + if (!response.ok) { + throw new Error("Failed to fetch posts!"); + } + + return response.json(); + }, + }); + + if (isLoading) { + return
Loading...
; + } + + /* do something with the data... */ + return
{JSON.stringify(posts)}
; +} +``` + + + + + Inside the `apps/web/src/lib/api/utils.ts` we're calling a function to get base url of your api, so make sure it's set correctly (especially on production) and your API endpoint is corresponding with the name there. + + ```tsx title="utils.ts" + export const getBaseUrl = () => { + if (typeof window !== "undefined") return window.location.origin; + if (env.NEXT_PUBLIC_URL) return env.NEXT_PUBLIC_URL; + if (env.VERCEL_URL) return `https://${env.VERCEL_URL}`; + return `http://localhost:${process.env.PORT ?? 3000}`; + }; + ``` + + As you can see we're mostly relying on the [environment variables](/docs/web/configuration/environment-variables) to get it, so there shouldn't be any issues with it, but in case, please be aware where to find it 😉 + + +## Handling responses + +As you can see in the examples above, the [Hono RPC](https://hono.dev/docs/guides/rpc) client returns a plain `Response` object, which you can use to get the data or handle errors. However, implementing this handling in every query or mutation can be tedious and will introduce unnecessary boilerplate in your codebase. + +That's why we've developed the `handle` function that unwraps the response for you, handles errors, and returns the data in a consistent format. You can safely use it with any procedure from the API client: + + + + ```tsx + // [!code word:handle] + import { handle } from "@turbostarter/api/utils"; + + import { api } from "~/lib/api/server"; + + export default async function MyServerComponent() { + const posts = await handle(api.posts.$get)(); + + /* do something with the data... */ + return
{JSON.stringify(posts)}
; + } + ``` +
+ + + ```tsx + // [!code word:handle] + + "use client"; + + import { handle } from "@turbostarter/api/utils"; + + import { api } from "~/lib/api/client"; + + export default function MyClientComponent() { + const { data: posts, isLoading } = useQuery({ + queryKey: ["posts"], + queryFn: handle(api.posts.$get), + }); + + if (isLoading) { + return
Loading...
; + } + + /* do something with the data... */ + return
{JSON.stringify(posts)}
; + } + ``` +
+
+ +With this approach, you can focus on the business logic instead of repeatedly writing code to handle API responses in your browser extension components, making your extension's codebase more readable and maintainable. + +The same error handling and response unwrapping benefits apply whether you're building web, mobile, or extension interfaces - allowing you to keep your data fetching logic consistent across all platforms. diff --git a/.context/turbostarter-framework-context/sections/web/api/internationalization.md b/.context/turbostarter-framework-context/sections/web/api/internationalization.md new file mode 100644 index 0000000..612428d --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/api/internationalization.md @@ -0,0 +1,54 @@ +--- +title: Internationalization +description: Learn how to localize and translate your API. +url: /docs/web/api/internationalization +--- + +# Internationalization + +Since TurboStarter provides fully featured [internationalization](/docs/web/internationalization/overview) out of the box, you can easily localize not only the frontend but also the API layer. This can be useful when you need to fetch localized data from the database or send emails in different languages. + +Let's explore possibilities of this feature. + +## Request-based localization + +To get the locale for the current request, you can leverage the `localize` middleware: + +```ts title="email/router.ts" +const emailRouter = new Hono().get("/", localize, (c) => { + const locale = c.var.locale; + + // do something with the locale +}); +``` + +Inside it, we're setting the `locale` variable in the current request context, making it available to the procedure. + +## Error handling + +When handling errors in an internationalized API, you'll want to ensure error messages are properly translated for your users. TurboStarter provides built-in support for localizing error messages using error codes and a special `onError` hook. + +That's why it's recommended to use error codes instead of direct messages in your throw statements: + +```ts +throw new HttpException(HttpStatusCode.UNAUTHORIZED, { + code: "auth:error.unauthorized", + /* 👇 optional */ + message: "You are not authorized to access this resource.", +}); +``` + +The error code will then be used to retrieve the localized message, and the returned response from your API will look like this: + +```json +{ + "code": "auth:error.unauthorized", + /* 👇 localized based on request's locale */ + "message": "You are not authorized to access this resource.", + "path": "/api/auth/login", + "status": 401, + "timestamp": "2024-01-01T00:00:00.000Z" +} +``` + +Then, you can either use the returned code to get the localized message in your frontend, or simply use the returned message as is. diff --git a/.context/turbostarter-framework-context/sections/web/api/mutations.md b/.context/turbostarter-framework-context/sections/web/api/mutations.md new file mode 100644 index 0000000..438b4e9 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/api/mutations.md @@ -0,0 +1,131 @@ +--- +title: Mutations +description: Learn how to mutate data on the server. +url: /docs/web/api/mutations +--- + +# Mutations + +As we saw in [adding new endpoint](/docs/web/api/new-endpoint#maybe-mutation), mutations allow us to modify data on the server, like creating, updating, or deleting resources. They can be defined similarly to queries using our API client. + +Just like queries, mutations can be executed either server-side or client-side depending on your needs. Let's explore both approaches. + +## Server actions + +Next.js provides [server actions](https://nextjs.org/docs/app/building-your-application/data-fetching/server-actions-and-mutations) as a powerful way to handle mutations directly on the server. They're particularly well-suited for form submissions and other data modifications. + +Using our `api` client with server actions is straightforward - you simply call the API function on the server. + +Here's an example of how you can define an action to create a new post: + + + + ```tsx + // [!code word:handle] + "use server"; + + import { revalidatePath } from "next/cache"; + + import { handle } from "@turbostarter/api/utils"; + + import { api } from "~/lib/api/server"; + + export async function createPost(post: PostInput) { + try { + await handle(api.posts.$post)(post); + } catch (error) { + onError(error); + } + + revalidatePath("/posts"); + } + ``` + + + + ```tsx + "use server"; + + import { revalidatePath } from "next/cache"; + + import { api } from "~/lib/api/server"; + + export async function createPost(post: PostInput) { + const response = await api.posts.$post(post); + + if (!response.ok) { + return { error: "Failed to create post" }; + } + + revalidatePath("/posts"); + } + ``` + + + +In the above example we're also using `revalidatePath` to revalidate the path `/posts` to fetch the updated list of posts. + + + + + + + +## useMutation hook + +On the other hand, if you want to perform a mutation on the client-side, you can use the `useMutation` hook that comes straight from the integration with [React Query](https://tanstack.com/query). + + + + ```tsx + // [!code word:handle] + import { handle } from "@turbostarter/api/utils"; + + import { api } from "~/lib/api/react"; + + export function CreatePost() { + const queryClient = useQueryClient(); + const { mutate } = useMutation({ + mutationFn: handle(api.posts.$post), + onSuccess: () => { + toast.success("Post created successfully!"); + queryClient.invalidateQueries({ queryKey: ["posts"] }); + }, + }); + + return
; + } + ``` + + + + ```tsx + import { api } from "~/lib/api/react"; + + export function CreatePost() { + const queryClient = useQueryClient(); + const { mutate } = useMutation({ + mutationFn: async (post: PostInput) => { + const response = await api.posts.$post(post); + + if (!response.ok) { + throw new Error("Failed to create post!"); + } + }, + onSuccess: () => { + toast.success("Post created successfully!"); + queryClient.invalidateQueries({ queryKey: ["posts"] }); + }, + }); + + return ; + } + ``` + + + + + + + + diff --git a/.context/turbostarter-framework-context/sections/web/api/new-endpoint.md b/.context/turbostarter-framework-context/sections/web/api/new-endpoint.md new file mode 100644 index 0000000..e82502b --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/api/new-endpoint.md @@ -0,0 +1,105 @@ +--- +title: Adding new endpoint +description: How to add new endpoint to the API. +url: /docs/web/api/new-endpoint +--- + +# Adding new endpoint + +To define a new API endpoint, you can either extend an existing entity (e.g. add new customer route) or create a new, separate module. + + + + ## Create new module + + To create a new module you can create a new folder in the `modules` folder. For example `modules/posts`. + + Then you would need to create a router declaration for this module. We're following a convention with the filename describing its purpose, so you would need to create a file named `router.ts` in the `modules/posts` folder. + + ```typescript title="modules/posts/router.ts" + import { Hono } from "hono"; + + import { validate } from "../../middleware"; + + export const postsRouter = new Hono().get( + "/", + validate("query", filtersSchema), + (c) => getAllPosts(c.req.valid("query")), + ); + ``` + + As you can see we're implementing a `.get` method without any additional middlewares for the router. This is a simple way to define a new GET endpoint. + + Also, we're using a [zod](https://zod.dev/) validator to ensure that input passed to the endpoint is correct. + + + + ### Maybe mutation? + + The same way you can define a mutation for the new entity, just by changing the `get` to `post`: + + ```ts title="modules/posts/router.ts" + // [!code word:.post] + export const postsRouter = new Hono().post( + "/", + enforceAuth, + validate("json", postSchema), + (c) => createPost(c.req.valid("json")), + ); + ``` + + Hono supports all [HTTP methods](https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods), so you can define a new endpoint for any method you need (e.g. `put`, `delete`, etc.). + + The `enforceAuth` middleware ensures that only authenticated users can access the endpoint, while the zod validator checks if the input data matches the expected schema. This combination provides both authentication and data validation in a single, clean setup. + + [Read more about protected routes](/docs/web/api/protected-routes). + + + + ## Implement logic + + Then you would need to create a controller for this module. There is a place, where the logic happens, e.g. for the `GET /` endpoint we would need to create a `getAllPosts` function which will fetch posts from the database. + + ```typescript title="modules/posts/queries.ts" + import { db } from "@turbostarter/db/server"; + import { posts } from "@turbostarter/db/schema"; + + export const getAllPosts = (filters: Filters) => { + return db.select().from(posts).all().where(/* your filter logic here */); + }; + ``` + + + + ## Register router + + To make the module and its endpoints available in the API you need to register a router for this module in the `index.ts` file: + + ```ts title="index.ts" + import { postsRouter } from "./modules/posts/router"; + + const appRouter = new Hono() + .basePath("/api") + .route("/posts", postsRouter) + /* other routers from your app logic */ + .onError(onError); + + type AppRouter = typeof appRouter; + + export type { AppRouter }; + export { appRouter }; + ``` + + The `basePath` method sets a prefix for all routes in this router. While optional, using it helps organize API endpoints. This modular approach makes the API structure clearer and easier to maintain. + + + +That's it! You've just created a new API endpoint - it's now available at `/api/posts` 🎉 + + + By exporting the `AppRouter` type you get fully type-safe RPC calls in the + client. It's important because without producing a huge amount of code, we're + fully type-safe from the frontend code. It helps avoid passing incorrect data + to the procedure and streamline consuming returned types without a need to + define these types by hand. + diff --git a/.context/turbostarter-framework-context/sections/web/api/overview.md b/.context/turbostarter-framework-context/sections/web/api/overview.md new file mode 100644 index 0000000..d7724de --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/api/overview.md @@ -0,0 +1,52 @@ +--- +title: Overview +description: Get started with the API. +url: /docs/web/api/overview +--- + +# Overview + +TurboStarter is designed to be a scalable and production-ready full-stack starter kit. One of its core features is a dedicated and extensible API layer. To enable this in a type-safe manner, we chose [Hono](https://hono.dev) as the API server and client library. + + + Hono is a small, simple, and ultrafast web framework that gives you a way to + define your API endpoints with full type safety. It provides built-in + middleware for common needs like validation, caching, and CORS. + + It also + includes an [RPC client](https://hono.dev/docs/guides/rpc) for making + type-safe function calls from the frontend. Being edge-first, it's optimized + for serverless environments and offers excellent performance. + + +All API endpoints and their resolvers are defined in the `packages/api` package. Here you will find a `modules` folder that contains the different feature modules of the API. Each module has its own folder and exports all its resolvers. + +For each module, we create a separate Hono router and then aggregate all sub-routers into one main router in the `index.ts` file. + +The API is then exposed as a route handler that will be provided as a Next.js API route: + +```ts title="apps/web/src/app/api/[...route]/route.ts" +import { handle } from "hono/vercel"; + +import { appRouter } from "@turbostarter/api"; + +const handler = handle(appRouter); +export { + handler as GET, + handler as POST, + handler as OPTIONS, + handler as PUT, + handler as PATCH, + handler as DELETE, + handler as HEAD, +}; +``` + + + The API is a part of web, serverless Next.js app. It means that you **must** + deploy it to use the API in other apps (e.g. mobile app, browser extension), + even if you don't need web app itself. It's very simple, as you're just + deploying the Next.js app and the API is just a part of it. + + +Learn more about API in the following sections: diff --git a/.context/turbostarter-framework-context/sections/web/api/protected-routes.md b/.context/turbostarter-framework-context/sections/web/api/protected-routes.md new file mode 100644 index 0000000..25d442d --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/api/protected-routes.md @@ -0,0 +1,124 @@ +--- +title: Protected routes +description: Learn how to protect your API routes. +url: /docs/web/api/protected-routes +--- + +# Protected routes + +Hono has built-in support for [middlewares](https://hono.dev/docs/guides/middleware), which are functions that can be used to modify the context or execute code before or after a route handler is executed. + +That's how we can secure our API endpoints from unauthorized access. Below are some examples of you can leverage middlewares to protect your API routes. + +## Authenticated access + +After validating the user's authentication status, we store their data in the context using [Hono's built-in context](https://hono.dev/docs/api/context). This allows us to access the user's information in subsequent middleware and procedures without having to re-validate the session. + +Here's an example of middleware that validates whether the user is currently logged in and stores their data in the context: + +```ts title="middleware.ts" +export const enforceAuth = createMiddleware<{ + Variables: { + user: User; + }; +}>(async (c, next) => { + const session = await auth.api.getSession({ headers: c.req.raw.headers }); + const user = session?.user ?? null; + + if (!user) { + throw new HTTPException(HttpStatusCode.UNAUTHORIZED, { + message: "You need to be logged in to access this feature!", + }); + } + + c.set("user", user); + await next(); +}); +``` + +Then we can use our defined middleware to protect endpoints by adding it before the route handler: + +```ts title="billing/router.ts" +export const billingRouter = new Hono().get( + "/customer", + enforceAuth, + async (c) => c.json(await getCustomerByUserId(c.var.user.id)), +); +``` + +## Role-based access + +In most cases, you will want to restrict access to certain endpoints based on the user's role. + +You can achieve this by creating a middleware that will check if the user has the required role and then pass the execution to the next middleware or procedure. + +E.g. for admin endpoints we want to ensure that the user has the `admin` role: + +```ts title="middleware.ts" +export const enforceAdmin = createMiddleware<{ + Variables: { + user: User; + }; +}>(async (c, next) => { + const user = c.var.user; + + if (!hasAdminPermission(user)) { + throw new HttpException(HttpStatusCode.FORBIDDEN, { + message: "You need to be an admin to access this feature!", + }); + } + + await next(); +}); +``` + +Then we can use our defined middleware to protect endpoints by adding it before the route handler: + +```ts title="admin/router.ts" +export const adminRouter = new Hono().get( + "/users", + enforceAuth, + enforceAdmin, + (c) => c.json(...), +); +``` + +## Feature-based access + +When developing your API you may want to restrict access to certain features based on the user's current subscription plan. (e.g. only users with "Pro" plan can access teams). + +You can achieve this by creating a middleware that will check if the user has access to the feature and then pass the execution to the next middleware or procedure: + +```ts title="middleware.ts" +export const enforceFeatureAvailable = (feature: Feature) => + createMiddleware<{ + Variables: { + user: User; + }; + }>(async (c, next) => { + const { data: customer } = await getCustomerById(c.var.user.id); + + const hasFeature = isFeatureAvailable(customer, feature); + + if (!hasFeature) { + throw new HTTPException(HttpStatusCode.PAYMENT_REQUIRED, { + message: "Upgrade your plan to access this feature!", + }); + } + + await next(); + }); +``` + +Use it within your procedure the same way as we did with `enforceAuth` middleware: + +```ts title="teams/router.ts" +export const teamsRouter = new Hono().get( + "/", + enforceAuth, + enforceFeatureAvailable(FEATURES.PRO.TEAMS), + (c) => c.json(...), +); +``` + +These are just examples of what you can achieve with Hono middlewares. You can use them to add any kind of logic to your API (e.g. [logging](https://hono.dev/docs/middleware/builtin/logger), [caching](https://hono.dev/docs/middleware/builtin/cache), etc.) diff --git a/.context/turbostarter-framework-context/sections/web/auth/2fa.md b/.context/turbostarter-framework-context/sections/web/auth/2fa.md new file mode 100644 index 0000000..b9afcce --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/auth/2fa.md @@ -0,0 +1,111 @@ +--- +title: Two-Factor Authentication (2FA) +description: Add an extra layer of security with two-factor authentication. +url: /docs/web/auth/2fa +--- + +# Two-Factor Authentication (2FA) + +TurboStarter uses [Better Auth's 2FA plugin](https://www.better-auth.com/docs/plugins/2fa) to provide multi-factor authentication (MFA) capabilities. Two-factor authentication adds an extra layer of security by requiring users to provide a second form of verification alongside their password. + +## Available methods + +TurboStarter supports multiple 2FA verification methods through Better Auth: + +* **TOTP (Time-based One-Time Password)** - codes generated by authenticator apps +* **OTP (One-Time Password)** - codes sent via email or SMS +* **Backup codes** - single-use recovery codes for account recovery + +You can use any TOTP-compatible authenticator app, such as: + +* [Google Authenticator](https://support.google.com/accounts/answer/1066447) +* [Authy](https://authy.com/) +* [Microsoft Authenticator](https://www.microsoft.com/en-us/security/mobile-authenticator-app) +* [1Password](https://1password.com/features/authenticator/) +* [Bitwarden](https://bitwarden.com/help/authenticator-keys/) + +## Enabling 2FA + + + + ### Enable in settings + + Users enable two-factor authentication in their account security settings. + + ![Enable 2FA](/images/docs/web/auth/two-factor/enable.png) + + + + ### Setup authenticator + + A QR code is displayed for users to scan with their authenticator app. + + ![Setup authenticator](/images/docs/web/auth/two-factor/authenticator-app.png) + + + + ### Verify setup + + Users enter a verification code from their authenticator to confirm setup. + + + + ### Backup codes + + Users receive single-use backup codes for account recovery. + + ![Backup codes](/images/docs/web/auth/two-factor/backup-codes.png) + + + + + Recovery codes are essential for account recovery if users lose access to + their authenticator device. Make sure to educate users about safely storing + their backup codes. + + +## Using 2FA + + + + ### Sign in normally + + Users enter their email and password or other methods as usual. + + + + ### 2FA prompt + + After successful password verification, users are prompted for their 2FA code. + + ![2FA prompt](/images/docs/web/auth/two-factor/sign-in-prompt.png) + + + + ### Enter verification code + + Users input the 6-digit code from their authenticator app. + + + + ### Access granted + + Upon successful verification, users gain access to their account. + + + +### Trusted devices + +Users can mark devices as trusted during 2FA verification. Trusted devices won't require 2FA verification for 60 days, providing a balance between security and convenience. + +## Configuration + +2FA is configured through Better Auth's plugin system. The plugin handles: + +* Secure secret generation and storage +* QR code generation for authenticator setup +* TOTP code validation +* Backup code generation and management +* Trusted device management + +For detailed implementation instructions, refer to the [Better Auth 2FA documentation](https://www.better-auth.com/docs/plugins/2fa). diff --git a/.context/turbostarter-framework-context/sections/web/auth/configuration.md b/.context/turbostarter-framework-context/sections/web/auth/configuration.md new file mode 100644 index 0000000..81dafdb --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/auth/configuration.md @@ -0,0 +1,138 @@ +--- +title: Configuration +description: Configure authentication for your application. +url: /docs/web/auth/configuration +--- + +# Configuration + +TurboStarter supports multiple different authentication methods: + +* **Password** - the traditional email/password method +* **Magic Link** - passwordless email link authentication +* **Passkey** - passkeys as an alternative to passwords +* **Anonymous** - guest mode for unauthenticated users +* **OAuth** - OAuth providers, [Apple](https://www.better-auth.com/docs/authentication/apple), [Google](https://www.better-auth.com/docs/authentication/google) and [Github](https://www.better-auth.com/docs/authentication/github) are set up by default + +All authentication methods are enabled by default, but you can easily customize them to your needs. You can enable or disable any method, and configure them according to your requirements. + + + Remember that you can mix and match these methods or add new ones - for + example, you can have both password and magic link authentication enabled at + the same time, giving your users more flexibility in how they authenticate. + + +Authentication configuration can be customized through a simple configuration file. The following sections explain the available options and how to configure each authentication method based on your requirements. + +## API + +The **server-side** authentication configuration is set at `packages/auth/src/server.ts`. It confgures [Better Auth](https://better-auth.com) package to use the correct providers and settings: + +```ts title="server.ts" +export const auth = betterAuth({ + emailAndPassword: { + enabled: true, + requireEmailVerification: true, + sendResetPassword: () => {}, + }, + emailVerification: { + sendOnSignUp: true, + autoSignInAfterVerification: true, + sendVerificationEmail: () => {}, + }, + database: drizzleAdapter(db, { + provider: "pg", + schema, + }), + plugins: [ + magicLink({ + sendMagicLink: () => {}, + }), + passkey(), + anonymous(), + expo(), + nextCookies(), + ], + socialProviders: { + [SocialProvider.APPLE]: { + clientId: env.APPLE_CLIENT_ID, + clientSecret: env.APPLE_CLIENT_SECRET, + appBundleIdentifier: env.APPLE_APP_BUNDLE_IDENTIFIER, + }, + [SocialProvider.GOOGLE]: { + clientId: env.GOOGLE_CLIENT_ID, + clientSecret: env.GOOGLE_CLIENT_SECRET, + }, + [SocialProvider.GITHUB]: { + clientId: env.GITHUB_CLIENT_ID, + clientSecret: env.GITHUB_CLIENT_SECRET, + }, + }, + + /* other configuration options */ +}); +``` + +The configuration is validated against Better Auth's schema at runtime, providing immediate feedback if any settings are incorrect or insecure. This validation ensures your authentication setup remains robust and properly configured. + +All authentication routes and handlers are centralized within the [Hono API](/docs/web/api/overview), giving you a single source of truth and complete control over the authentication flow. This centralization makes it easier to maintain, debug, and customize the authentication process as needed. + +[Read more about it in the official documentation](https://www.better-auth.com/docs/basic-usage). + +## UI + +We have separate configuration that determines what is displayed to your users in the **UI**. It's set at `apps/web/config/auth.ts`. + +The recommendation is to **not update this directly** - instead, please define the environment variables and override the default behavior. + +```ts title="apps/web/config/auth.ts" +import env from "env.config"; + +import { SocialProvider, authConfigSchema } from "@turbostarter/auth"; + +import type { AuthConfig } from "@turbostarter/auth"; + +export const authConfig = authConfigSchema.parse({ + providers: { + password: env.NEXT_PUBLIC_AUTH_PASSWORD, + magicLink: env.NEXT_PUBLIC_AUTH_MAGIC_LINK, + passkey: env.NEXT_PUBLIC_AUTH_PASSKEY, + anonymous: env.NEXT_PUBLIC_AUTH_ANONYMOUS, + oAuth: [SocialProvider.APPLE, SocialProvider.GOOGLE, SocialProvider.GITHUB], + }, +}) satisfies AuthConfig; +``` + +The configuration is also validated using the Zod schema, so if something is off, you'll see the errors. + +For example, if you want to switch from password to magic link, you'd change the following environment variables: + +```dotenv title=".env.local" +NEXT_PUBLIC_AUTH_PASSWORD=false +NEXT_PUBLIC_AUTH_MAGIC_LINK=true +``` + +To display third-party providers in the UI, you need to set the `oAuth` array to include the provider you want to display. The default is Apple, Google and Github: + +```tsx title="apps/web/config/auth.ts" +providers: { + ... + oAuth: [SocialProvider.APPLE, SocialProvider.GOOGLE, SocialProvider.GITHUB], + ... +}, +``` + +## Third party providers + +To enable third-party authentication providers, you'll need to: + +1. Set up an OAuth application in the provider's developer console (like [Apple Developer Portal](https://developer.apple.com/account/), [Google Cloud Console](https://console.cloud.google.com/), [Github Developer Settings](https://github.com/settings/developers) or any other provider you want to use) +2. Configure the corresponding environment variables in your TurboStarter application + +Each OAuth provider requires its own set of credentials and environment variables. Please refer to the [Better Auth documentation](https://better-auth.com/docs/concepts/oauth) for detailed setup instructions for each supported provider. + + + Make sure to set both development and production environment variables + appropriately. Your OAuth provider may require different callback URLs for + each environment. + diff --git a/.context/turbostarter-framework-context/sections/web/auth/flow.md b/.context/turbostarter-framework-context/sections/web/auth/flow.md new file mode 100644 index 0000000..e8f4c8b --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/auth/flow.md @@ -0,0 +1,53 @@ +--- +title: User flow +description: Discover the authentication flow in Turbostarter. +url: /docs/web/auth/flow +--- + +# User flow + +TurboStarter ships with a fully functional authentication system. Most of the views and components are preconfigured and easily customizable to your needs. + +Here you will find a quick walkthrough of the authentication flow. + +## Sign up + +The sign-up page is where users can create an account. They need to provide their email address and password. + +![Sign up](/images/docs/web/auth/sign-up.png) + +Once successful, users are asked to confirm their email address. This is enabled by default - and due to security reasons, it's not possible to disable it. + + + Make sure to configure the [email provider](/docs/web/emails/configuration) together with the [auth hooks](/docs/web/emails/sending#authentication-emails) to be able to send emails from your app. + + +![Confirm email](/images/docs/web/auth/confirm-email.png) + +## Sign in + +The sign-in page is where users can log in to their account. They need to provide their email address and password, use magic link (if enabled) or third-party providers. + +![Sign in](/images/docs/web/auth/sign-in.png) + +## Sign out + +The sign out button is located in the user menu. + +![Sign out](/images/docs/web/auth/sign-out.png) + +## Forgot password + +The forgot password page is where users can reset their password. They need to provide their email address and follow the instructions in the email. + +![Forgot password](/images/docs/web/auth/forgot-password.png) + +The reset password page is where users land from a forgot email. There they can reset their password by providing new password and confirming it. + +![Reset password](/images/docs/web/auth/update-password.png) + +## Two-factor authentication + +Two-factor authentication is a security feature that requires users to provide a code sent to their email or phone number in addition to their password when logging in. + +![Two-factor authentication](/images/docs/web/auth/two-factor/sign-in-prompt.png) diff --git a/.context/turbostarter-framework-context/sections/web/auth/oauth.md b/.context/turbostarter-framework-context/sections/web/auth/oauth.md new file mode 100644 index 0000000..d76310a --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/auth/oauth.md @@ -0,0 +1,56 @@ +--- +title: OAuth +description: Get started with social authentication. +url: /docs/web/auth/oauth +--- + +# OAuth + +Better Auth supports over **30** (!) different [OAuth providers](https://www.better-auth.com/docs/concepts/oauth). They can be easily configured and enabled in the kit without any additional configuration needed. + + + TurboStarter provides you with all the configuration required to handle OAuth providers responses from your app: + + * redirects + * middleware + * confirmation API routes + + You just need to configure one of the below providers on their side and set correct credentials as environment variables in your TurboStarter app. + + +![OAuth providers](/images/docs/web/auth/social-providers.png) + +Third Party providers need to be configured, managed and enabled fully on the provider's side. TurboStarter just needs the correct credentials to be set as environment variables in your app and passed to the [authentication API configuration](/docs/web/auth/configuration#api). + +To enable OAuth providers in your TurboStarter app, you need to: + +1. Set up an OAuth application in the provider's developer console (like [Apple Developer Portal](https://developer.apple.com/account/), [Google Cloud Console](https://console.cloud.google.com/), [Github Developer Settings](https://github.com/settings/developers) or any other provider you want to use) +2. Configure the provider's credentials as environment variables in your app. For example, for Google OAuth: + +```dotenv title="apps/web/.env.local" +GOOGLE_CLIENT_ID= +GOOGLE_CLIENT_SECRET= +``` + +Then, pass it to the authentication configuration in `packages/auth/src/server.ts`: + +```ts title="server.ts" +export const auth = betterAuth({ + ... + + socialProviders: { + [SocialProvider.GOOGLE]: { + clientId: env.GOOGLE_CLIENT_ID, + clientSecret: env.GOOGLE_CLIENT_SECRET, + }, + }, + + ... +}); +``` + + + Better Auth provides a [generic OAuth plugin](https://www.better-auth.com/docs/plugins/generic-oauth) that allows you to add any OAuth provider to your app. + + It supports both OAuth 2.0 and OpenID Connect (OIDC) flows, allowing you to easily add social login or custom OAuth authentication to your application. + diff --git a/.context/turbostarter-framework-context/sections/web/auth/overview.md b/.context/turbostarter-framework-context/sections/web/auth/overview.md new file mode 100644 index 0000000..1333271 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/auth/overview.md @@ -0,0 +1,39 @@ +--- +title: Overview +description: Get started with authentication. +url: /docs/web/auth/overview +--- + +# Overview + +TurboStarter uses [Better Auth](https://better-auth.com) to handle authentication. It's a secure, production-ready authentication solution that integrates seamlessly with many frameworks and provides enterprise-grade security out of the box. + + + One of the core principles of TurboStarter is to do things **as simple as possible**, and to make everything **as performant as possible**. + + Better Auth provides an excellent developer experience with minimal configuration required, while maintaining enterprise-grade security standards. Its framework-agnostic approach and focus on performance makes it the perfect choice for TurboStarter. + + Recently, Better Auth [announced](https://www.better-auth.com/blog/authjs-joins-better-auth) an incorporation of [Auth.js (27k+ stars on Github)](https://authjs.dev/), making it even more powerful and flexible. + + +![Better Auth](/images/docs/better-auth.png) + +You can read more about Better Auth in the [official documentation](https://better-auth.com/docs). + +TurboStarter supports multiple authentication methods: + +* **Password** - the traditional email/password method +* **Magic Link** - magic links +* **Passkey** - passkeys ([WebAuthn](https://developer.mozilla.org/en-US/docs/Web/API/Web_Authentication_API)) +* **Anonymous** - allowing users to proceed anonymously +* **OAuth** - OAuth social providers ([Apple](https://www.better-auth.com/docs/authentication/apple), [Google](https://www.better-auth.com/docs/authentication/google) and [Github](https://www.better-auth.com/docs/authentication/github) preconfigured) + +As well as common applications flows, with ready-to-use views and components: + +* **Sign in** - sign in with email/password or OAuth providers +* **Sign up** - sign up with email/password or OAuth providers +* **Sign out** - sign out +* **Password recovery** - forgot and reset password +* **Email verification** - verify email + +You can construct your auth flow like LEGO bricks - plug in needed parts and customize them to your needs. diff --git a/.context/turbostarter-framework-context/sections/web/background-tasks/overview.md b/.context/turbostarter-framework-context/sections/web/background-tasks/overview.md new file mode 100644 index 0000000..1b10ac2 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/background-tasks/overview.md @@ -0,0 +1,122 @@ +--- +title: Overview +description: Learn about background tasks & cron jobs and how they can power your application. +url: /docs/web/background-tasks/overview +--- + +# Overview + +Background tasks and cron jobs are long-running processes that execute outside of your main application flow, allowing you to handle time-intensive operations and scheduled workflows without blocking user interactions or hitting serverless function timeouts. + + + Background tasks are ideal for operations that take longer than typical serverless function timeouts (10-60 seconds), such as processing large files, sending batch emails, or making multiple API calls. + + Cron jobs are perfect for recurring operations like daily reports, cleanup tasks, or periodic data synchronization. + + +## What are background tasks? + +**Background tasks** are asynchronous processes that run separately from your main application thread. Instead of forcing users to wait for lengthy operations to complete, you can offload these tasks to run in the background while your application remains responsive. + +**Cron jobs** are scheduled background tasks that run automatically at specific times or intervals. They're perfect for maintenance operations, reports, and recurring workflows that need to happen without user intervention. + +Think of background tasks as your application's *worker threads* - they handle the heavy lifting while your main application stays fast and responsive for users. + + + +## Why use background tasks? + + + + Most serverless platforms have strict execution limits: + + * **[Vercel (Hobby)](https://vercel.com/docs/functions/serverless-functions/runtimes#max-duration)**: 300 seconds + * **[Vercel (Pro)](https://vercel.com/docs/functions/serverless-functions/runtimes#max-duration)**: 800 seconds + * **[Vercel (Enterprise)](https://vercel.com/docs/functions/serverless-functions/runtimes#max-duration)**: 800 seconds + * **[AWS Lambda](https://docs.aws.amazon.com/lambda/latest/dg/configuration-timeout.html)**: 900 seconds + * **[Netlify Functions](https://docs.netlify.com/functions/overview/#default-deployment-options)**: 30 seconds + + Background tasks let you bypass these limitations entirely. + + + + Users don't have to wait for long-running processes. They can continue using + your application while tasks complete in the background. + + + + Cron jobs enable hands-off automation of recurring tasks like daily backups, + weekly reports, or monthly user engagement analysis - all running reliably + without manual intervention. + + + + Background tasks can be automatically retried if they fail, ensuring your + critical processes eventually complete successfully. + + + + Your main application servers stay available to handle user requests instead of being tied up with heavy processing tasks. + + + +## Common use cases + +Here are some typical scenarios where background tasks shine: + + + + * **Video transcoding**: Converting uploaded videos to different formats or resolutions + * **Image optimization**: Batch processing user-uploaded images + * **Document parsing**: Extracting text from PDFs or generating thumbnails + + + + * **Database migrations**: Moving or transforming large datasets + * **Report generation**: Creating complex analytics reports + * **Data synchronization**: Syncing data between different systems + + + + * **Email campaigns**: Sending personalized emails to large user lists + * **Notification processing**: Delivering push notifications across multiple platforms + * **SMS campaigns**: Bulk SMS sending with rate limiting + + + + * **Content generation**: Using AI models to generate text, images, or videos + * **Data analysis**: Running machine learning models on large datasets + * **Natural language processing**: Analyzing text content for insights + + + + * **API synchronization**: Syncing data with external services + * **Webhook processing**: Handling incoming webhooks that trigger complex workflows + * **Social media automation**: Posting content across multiple platforms + + + + * **Daily reports**: Generating and emailing daily analytics or performance reports + * **Database maintenance**: Cleaning up old records, optimizing indexes, or running backups + * **User engagement**: Sending weekly newsletters or monthly account summaries + * **System monitoring**: Health checks, performance monitoring, and alert notifications + * **Content management**: Auto-publishing scheduled content or archiving old posts + + + +## When not to use background tasks? + +Background tasks and cron jobs aren't always the right solution. Consider alternatives for: + +* **Real-time operations**: Tasks that users need immediate results from +* **Simple, fast operations**: Tasks that complete in under 5-10 seconds +* **Database queries**: Standard CRUD operations that should remain synchronous +* **User authentication**: Login/logout processes should be immediate + + + Start with synchronous processing for simple tasks and manual processes for infrequent operations. Only move to background tasks when you hit timeout limitations or user experience issues, and only use cron jobs when you need reliable automation. + + +## Getting started + +Ready to add background tasks to your TurboStarter application? Check out our [Trigger.dev integration guide](/docs/web/background-tasks/trigger) or [Upstash QStash integration guide](/docs/web/background-tasks/qstash) to learn how to implement background tasks using one of the most developer-friendly background job frameworks available. diff --git a/.context/turbostarter-framework-context/sections/web/background-tasks/qstash.md b/.context/turbostarter-framework-context/sections/web/background-tasks/qstash.md new file mode 100644 index 0000000..d249018 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/background-tasks/qstash.md @@ -0,0 +1,648 @@ +--- +title: Upstash QStash +description: Integrate Upstash QStash with your TurboStarter application for serverless-first background task processing. +url: /docs/web/background-tasks/qstash +--- + +# Upstash QStash + +[Upstash QStash](https://upstash.com/docs/qstash) is a serverless message queue and task scheduler designed specifically for serverless and edge environments. It uses HTTP endpoints instead of persistent connections, making it perfect for modern web applications. + + + QStash is built for the serverless world - no infrastructure to manage, automatic scaling, and pay-per-use pricing. It delivers messages to your HTTP endpoints with built-in retries, delays, and scheduling capabilities. + + + + + ## Setup + + Visit [Upstash Console](https://console.upstash.com) and create a free account. Create a new QStash project and note down your credentials. + + Add your QStash credentials to your root environment variables: + + ```dotenv title=".env.local" + QSTASH_URL=https://qstash.upstash.io + QSTASH_TOKEN=your_qstash_token_here + QSTASH_CURRENT_SIGNING_KEY=your_current_signing_key_here + QSTASH_NEXT_SIGNING_KEY=your_next_signing_key_here + ``` + + You can find these values in your Upstash Console under the QStash project settings. + + For production, make sure to add these environment variables to your deployment platform. + + + + ## Install dependencies + + Add the QStash SDK to your API package: + + ```bash + pnpm add --filter api @upstash/qstash + ``` + + + + ## Create the QStash client + + Create a utility file to initialize the QStash client in your API package: + + ```ts title="packages/api/src/lib/qstash.ts" + import { Client } from "@upstash/qstash"; + + import { env } from "~/env"; + + export const qstashClient = new Client({ + baseUrl: env.QSTASH_URL, + token: env.QSTASH_TOKEN, + }); + ``` + + + + ## Create task handlers + + QStash delivers messages to HTTP endpoints, so you'll create API routes to handle your background tasks. + + Let's create task handlers for common operations: + + + + ```ts title="packages/api/src/modules/tasks/router.ts" + import { Hono } from "hono"; + import * as z from "zod"; + + import { qstashVerifyMiddleware } from "../../middleware/qstash-verify"; + import { dailyCleanupHandler } from "./handlers/daily-cleanup"; + import { processUserDataHandler } from "./handlers/process-user-data"; + + const processUserDataSchema = z.object({ + userId: z.string(), + operation: z.enum(["export", "analyze", "cleanup"]), + }); + + export const tasksRouter = new Hono() + .basePath("/tasks") + // Apply QStash signature verification to all task routes + .use(qstashVerifyMiddleware) + .post("/process-user-data", processUserDataHandler) + .post("/daily-cleanup", dailyCleanupHandler); + ``` + + + + ```ts title="packages/api/src/modules/tasks/handlers/process-user-data.ts" + import type { Context } from "hono"; + import * as z from "zod"; + + const ProcessUserDataSchema = z.object({ + userId: z.string(), + operation: z.enum(["export", "analyze", "cleanup"]), + }); + + export async function processUserDataHandler(c: Context) { + try { + const payload = ProcessUserDataSchema.parse(await c.req.json()); + const { userId, operation } = payload; + + console.log("Starting user data processing", { userId, operation }); + + switch (operation) { + case "export": + // Simulate data export + await new Promise((resolve) => setTimeout(resolve, 2000)); + console.log("User data exported successfully"); + return c.json({ + success: true, + result: "Data exported to CSV", + }); + + case "analyze": + // Simulate data analysis + await new Promise((resolve) => setTimeout(resolve, 5000)); + console.log("User data analysis completed"); + return c.json({ + success: true, + result: { totalActions: 156, avgSessionTime: "4m 32s" }, + }); + + case "cleanup": + // Simulate data cleanup + await new Promise((resolve) => setTimeout(resolve, 3000)); + console.log("User data cleanup completed"); + return c.json({ + success: true, + result: "Removed 23 obsolete records", + }); + + default: + throw new Error(`Unknown operation: ${operation}`); + } + } catch (error) { + console.error("Task failed:", error); + return c.json({ error: "Task failed" }, 500); + } + } + ``` + + + + ```ts title="packages/api/src/modules/tasks/handlers/daily-cleanup.ts" + import type { Context } from "hono"; + + export async function dailyCleanupHandler(c: Context) { + try { + console.log("Starting daily cleanup"); + + // Cleanup old logs + await new Promise((resolve) => setTimeout(resolve, 5000)); + console.log("Logs cleaned up"); + + // Cleanup temporary files + await new Promise((resolve) => setTimeout(resolve, 3000)); + console.log("Temp files cleaned up"); + + // Generate daily reports + await new Promise((resolve) => setTimeout(resolve, 8000)); + console.log("Reports generated"); + + return c.json({ + success: true, + cleanupTime: new Date().toISOString(), + itemsProcessed: 1247, + }); + } catch (error) { + console.error("Daily cleanup failed:", error); + return c.json({ error: "Daily cleanup failed" }, 500); + } + } + ``` + + + + ```ts title="packages/api/src/middleware/qstash-verify.ts" + import { Receiver } from "@upstash/qstash"; + import { createMiddleware } from "hono/factory"; + + export const qstashVerifyMiddleware = createMiddleware(async (c, next) => { + const currentSigningKey = process.env.QSTASH_CURRENT_SIGNING_KEY; + const nextSigningKey = process.env.QSTASH_NEXT_SIGNING_KEY; + + if (!currentSigningKey || !nextSigningKey) { + return c.json({ error: "QStash signing keys not configured" }, 500); + } + + const signature = c.req.header("upstash-signature"); + + if (!signature) { + return c.json({ error: "Missing QStash signature" }, 401); + } + + try { + const body = await c.req.text(); + + const receiver = new Receiver({ + currentSigningKey, + nextSigningKey, + }); + + const isValid = receiver.verify({ + body, + signature, + }); + + if (!isValid) { + return c.json({ error: "Invalid QStash signature" }, 401); + } + + // Re-create the request with the body for the next handler + const newRequest = new Request(c.req.url, { + method: c.req.method, + headers: c.req.headers, + body, + }); + + c.req = newRequest; + await next(); + } catch (error) { + console.error("QStash signature verification failed:", error); + return c.json({ error: "Invalid signature" }, 401); + } + }); + ``` + + + + + + ## Register task routes + + Add the tasks router to your main API: + + ```ts title="packages/api/src/index.ts" + import { tasksRouter } from "./modules/tasks/router"; + + const appRouter = new Hono() + .basePath("/api") + .route("/tasks", tasksRouter) + // ... other existing routers + .onError(onError); + + export { appRouter }; + ``` + + + + ## Triggering tasks + + You can trigger tasks from your TurboStarter application by publishing messages to QStash, which will then deliver them to your task endpoints. + + Create a service to handle task triggering: + + ```ts title="packages/api/src/modules/tasks/service.ts" + import { qstashClient } from "../../lib/qstash"; + + function getTaskUrl(taskName: string): string { + const baseUrl = process.env.NEXT_PUBLIC_APP_URL || "http://localhost:3000"; + return `${baseUrl}/api/tasks/${taskName}`; + } + + export class TaskService { + static async processUserData( + userId: string, + operation: "export" | "analyze" | "cleanup", + ) { + return await qstashClient.publishJSON({ + url: getTaskUrl("process-user-data"), + body: { userId, operation }, + }); + } + + static async scheduleUserDataProcessing( + userId: string, + operation: "export" | "analyze" | "cleanup", + delaySeconds: number, + ) { + return await qstashClient.publishJSON({ + url: getTaskUrl("process-user-data"), + body: { userId, operation }, + delay: `${delaySeconds}s`, + }); + } + + static async scheduleDailyCleanup() { + return await qstashClient.schedules.create({ + destination: getTaskUrl("daily-cleanup"), + cron: "0 2 * * *", // Daily at 2 AM + }); + } + } + ``` + + + + ## Create API endpoints for triggering + + Create endpoints to trigger tasks from your application: + + ```ts title="packages/api/src/modules/tasks/trigger/router.ts" + import { Hono } from "hono"; + import * as z from "zod"; + + import { enforceAuth, validate } from "../../middleware"; + import { TaskService } from "./service"; + + const triggerUserDataSchema = z.object({ + userId: z.string(), + operation: z.enum(["export", "analyze", "cleanup"]), + delaySeconds: z.number().optional(), + }); + + export const taskTriggerRouter = new Hono() + .post( + "/trigger/process-user-data", + enforceAuth, + validate("json", triggerUserDataSchema), + async (c) => { + const { userId, operation, delaySeconds } = c.req.valid("json"); + + const result = delaySeconds + ? await TaskService.scheduleUserDataProcessing( + userId, + operation, + delaySeconds, + ) + : await TaskService.processUserData(userId, operation); + + return c.json({ + success: true, + messageId: result.messageId, + message: delaySeconds + ? `Task scheduled to run in ${delaySeconds} seconds` + : "Task queued for immediate processing", + }); + }, + ) + .post("/trigger/daily-cleanup", enforceAuth, async (c) => { + const result = await TaskService.scheduleDailyCleanup(); + + return c.json({ + success: true, + scheduleId: result.scheduleId, + message: "Daily cleanup scheduled", + }); + }); + ``` + + Add it to your main router: + + ```ts title="packages/api/src/index.ts" + import { taskTriggerRouter } from "./modules/tasks/trigger/router"; + + const appRouter = new Hono() + .basePath("/api") + .route("/tasks", tasksRouter) + .route("/", taskTriggerRouter) // Trigger routes at root level + // ... other existing routers + .onError(onError); + + export { appRouter }; + ``` + + + + ## Using tasks in your application + + ### From the client + + ```tsx title="apps/web/src/modules/tasks/process-data-button.tsx" + "use client"; + + import { handle } from "@turbostarter/api/utils"; + import { useMutation } from "@tanstack/react-query"; + + import { api } from "~/lib/api/client"; + + export function ProcessDataButton({ userId }: { userId: string }) { + const { mutate: processData, isPending } = useMutation({ + mutationFn: handle(api.trigger["process-user-data"].$post), + onSuccess: (data) => { + console.log("Task queued:", data.messageId); + }, + }); + + return ( + + ); + } + ``` + + ### From a server action + + ```ts title="apps/web/src/app/actions/user-actions.ts" + "use server"; + + import { handle } from "@turbostarter/api/utils"; + + import { api } from "~/lib/api/server"; + + export async function processUserData( + userId: string, + operation: "export" | "analyze" | "cleanup", + ) { + try { + const result = await handle(api.trigger["process-user-data"].$post)({ + json: { userId, operation }, + }); + + return { + success: true, + messageId: result.messageId, + }; + } catch (error) { + console.error("Failed to queue background task:", error); + throw new Error("Failed to queue background task"); + } + } + ``` + + + +## Advanced features + +### Cron jobs & scheduling + +QStash makes it easy to schedule recurring tasks: + +```ts +// Schedule a task to run every day at 2 AM +await qstashClient.schedules.create({ + destination: `${baseUrl}/api/tasks/daily-cleanup`, + cron: "0 2 * * *", +}); + +// Schedule a task to run every Monday at 9 AM +await qstashClient.schedules.create({ + destination: `${baseUrl}/api/tasks/weekly-report`, + cron: "0 9 * * 1", +}); + +// One-time delayed task +await qstashClient.publishJSON({ + url: `${baseUrl}/api/tasks/reminder`, + body: { userId: "123", type: "follow-up" }, + delay: "3d", // 3 days from now +}); +``` + +### Topics (Fanout pattern) + +Create topics to send messages to multiple endpoints: + +```ts +// Create a topic +await qstashClient.topics.upsert({ + name: "user-events", + endpoints: [ + { url: `${baseUrl}/api/tasks/update-analytics` }, + { url: `${baseUrl}/api/tasks/send-notification` }, + { url: `${baseUrl}/api/tasks/update-crm` }, + ], +}); + +// Publish to topic - all endpoints will receive the message +await qstashClient.publishJSON({ + topic: "user-events", + body: { + userId: "123", + event: "user-registered", + timestamp: new Date().toISOString(), + }, +}); +``` + +### Queues (Sequential processing) + +Create queues for ordered task processing: + +```ts +// Create a queue +const queue = qstashClient.queue({ queueName: "user-onboarding" }); + +// Add tasks to queue (they'll run in order) +await queue.enqueueJSON({ + url: `${baseUrl}/api/tasks/send-welcome-email`, + body: { userId: "123" }, +}); + +await queue.enqueueJSON({ + url: `${baseUrl}/api/tasks/setup-user-profile`, + body: { userId: "123" }, +}); + +await queue.enqueueJSON({ + url: `${baseUrl}/api/tasks/trigger-onboarding-sequence`, + body: { userId: "123" }, +}); +``` + +## Monitoring and debugging + +### QStash Dashboard + +Visit the [Upstash Console](https://console.upstash.com) to monitor your tasks: + +* **Message tracking**: See all messages, their status, and delivery attempts +* **Logs**: View detailed logs for each message delivery +* **Analytics**: Monitor throughput, success rates, and error patterns +* **Schedules**: Manage and monitor your cron jobs +* **Dead letter queue**: Handle messages that failed after all retries + +### Local development + +During development, you can: + +1. **Use ngrok** for local testing: + + ```bash + # Install ngrok + npm install -g ngrok + + # Expose your local server + ngrok http 3000 + + # Use the ngrok URL in your QStash configuration + ``` + +2. **Check message delivery** in the Upstash Console + +3. **Use console.log** in your task handlers for debugging + +## Best practices + + + + Use the QStash signature verification middleware to ensure messages are authentic: + + ```ts + // ✅ Good - Always verify QStash signatures + .use(qstashVerifyMiddleware) + + // ❌ Not secure - Accepting unverified requests + .post("/tasks/sensitive-operation", handler) + ``` + + + + Return appropriate HTTP status codes so QStash knows whether to retry: + + ```ts + // ✅ Good - Clear error handling + try { + await processTask(payload); + return c.json({ success: true }); + } catch (error) { + console.error("Task failed:", error); + // 5xx = QStash will retry, 4xx = won't retry + return c.json({ error: "Task failed" }, 500); + } + ``` + + + + Make your tasks safe to run multiple times in case of retries: + + ```ts + // ✅ Good - Check if work already done + const existingResult = await db.findProcessedResult(payload.id); + if (existingResult) { + return c.json({ success: true, result: existingResult }); + } + + // Proceed with processing... + ``` + + + + Configure timeouts based on your expected processing time: + + ```ts + // For quick tasks + await qstashClient.publishJSON({ + url: taskUrl, + body: payload, + timeout: "30s", + }); + + // For longer tasks + await qstashClient.publishJSON({ + url: taskUrl, + body: payload, + timeout: "300s", // 5 minutes + }); + ``` + + + + Include relevant context in your logs: + + ```ts + console.log("Task started", { + taskType: "process-user-data", + userId: payload.userId, + operation: payload.operation, + timestamp: new Date().toISOString(), + }); + ``` + + + +## Next steps + +With QStash integrated into your TurboStarter application, you can now: + +* **Process background tasks** without worrying about serverless timeouts +* **Schedule recurring operations** with reliable cron job functionality +* **Handle high-volume messaging** with automatic retries and scaling +* **Build complex workflows** using topics, queues, and delays + +Ready to explore more advanced features? Check out the official documentation for webhooks, batch operations, and advanced routing patterns. + + + + + + diff --git a/.context/turbostarter-framework-context/sections/web/background-tasks/trigger.md b/.context/turbostarter-framework-context/sections/web/background-tasks/trigger.md new file mode 100644 index 0000000..13bbc99 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/background-tasks/trigger.md @@ -0,0 +1,505 @@ +--- +title: trigger.dev +description: Integrate trigger.dev with your TurboStarter application for reliable background task processing. +url: /docs/web/background-tasks/trigger +--- + +# trigger.dev + +[trigger.dev](https://trigger.dev) is an open-source background jobs framework that lets you write reliable workflows in plain async code. + + + trigger.dev provides automatic retries, real-time monitoring, and seamless scaling - all while letting you write background tasks in familiar JavaScript/TypeScript code directly in your TurboStarter project. + + + + + ## Setup + + Visit [trigger.dev](https://trigger.dev) and create a free account. Create a new project and note down your API key. + + Add your trigger.dev API key to your root environment variables: + + ```dotenv title=".env.local" + TRIGGER_SECRET_KEY=your_secret_key_here + ``` + + For production, make sure to add the production API key to your deployment environment. + + + + ## Create a new package in your repository + + You can use the [Turbo generator](/docs/web/customization/add-package) to quickly scaffold the package structure: + + ```bash + turbo gen package + ``` + + When prompted, name your package `tasks`. This will create the basic structure for you. + + Alternatively, create a new folder `tasks` in the `/packages` directory and add the following files: + + + + ```json + { + "name": "@turbostarter/tasks", + "private": true, + "version": "0.1.0", + "type": "module", + "exports": { + ".": "./src/index.ts" + }, + "scripts": { + "clean": "git clean -xdf .cache .turbo dist node_modules", + "dev": "pnpm dlx trigger.dev@latest dev", + "deploy": "pnpm dlx trigger.dev@latest deploy", + "format": "prettier --check . --ignore-path ../../.gitignore", + "lint": "eslint", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@trigger.dev/sdk": "3.3.17" + }, + "devDependencies": { + "@trigger.dev/build": "3.3.17", + "@turbostarter/eslint-config": "workspace:*", + "@turbostarter/prettier-config": "workspace:*", + "@turbostarter/tsconfig": "workspace:*", + "eslint": "catalog:", + "prettier": "catalog:", + "typescript": "catalog:" + }, + "prettier": "@turbostarter/prettier-config" + } + ``` + + + + ```json + { + "extends": "@turbostarter/tsconfig/base.json", + "include": ["**/*.ts"], + "exclude": ["dist", "build", "node_modules"] + } + ``` + + + + ```ts + import { defineConfig } from "@trigger.dev/sdk"; + + export default defineConfig({ + project: "your_project_id", // Replace with your actual project ID + runtime: "node", + logLevel: "log", + maxDuration: 300, + dirs: ["./src/trigger"], + }); + ``` + + + + + + ## Create your first task + + Now create your first task in the `packages/tasks/src/trigger` directory: + + + + ```ts title="packages/tasks/src/trigger/process-user-data.ts" + import { task, logger, wait } from "@trigger.dev/sdk"; + import * as z from "zod"; + + const ProcessUserDataSchema = z.object({ + userId: z.string(), + operation: z.enum(["export", "analyze", "cleanup"]), + }); + + export const processUserDataTask = task({ + id: "process-user-data", + run: async (payload: z.infer) => { + const { userId, operation } = payload; + + logger.info("Starting user data processing", { userId, operation }); + + switch (operation) { + case "export": + await wait.for({ seconds: 2 }); + logger.info("User data exported successfully"); + return { success: true, result: "Data exported to CSV" }; + + case "analyze": + await wait.for({ seconds: 5 }); + logger.info("User data analysis completed"); + return { + success: true, + result: { totalActions: 156, avgSessionTime: "4m 32s" }, + }; + + case "cleanup": + await wait.for({ seconds: 3 }); + logger.info("User data cleanup completed"); + return { success: true, result: "Removed 23 obsolete records" }; + + default: + throw new Error(`Unknown operation: ${operation}`); + } + }, + }); + ``` + + + + ```ts title="packages/tasks/src/trigger/daily-cleanup.ts" + import { schedules, task, logger, wait } from "@trigger.dev/sdk"; + + export const dailyCleanupTask = task({ + id: "daily-cleanup", + run: async () => { + logger.info("Starting daily cleanup"); + + // Cleanup old logs + await wait.for({ seconds: 5 }); + logger.info("Logs cleaned up"); + + // Cleanup temporary files + await wait.for({ seconds: 3 }); + logger.info("Temp files cleaned up"); + + // Generate daily reports + await wait.for({ seconds: 8 }); + logger.info("Reports generated"); + + return { + success: true, + cleanupTime: new Date().toISOString(), + itemsProcessed: 1247, + }; + }, + }); + + // Schedule the task to run daily at 2 AM + schedules.create({ + task: "daily-cleanup", + cron: "0 2 * * *", + }); + ``` + + + + ```ts title="packages/tasks/src/index.ts" + export * from "./trigger/process-user-data"; + export * from "./trigger/daily-cleanup"; + ``` + + + + + + ## Test your task + + You can test your tasks locally by running: + + ```bash + # Start the development server + pnpm --filter @turbostarter/tasks dev + ``` + + This will deploy your tasks to trigger.dev in the development environment, allowing you to trigger them from the dashboard or programmatically. + + + + ## Deploy your tasks + + To deploy your tasks to production on trigger.dev, run: + + ```bash + pnpm --filter @turbostarter/tasks deploy + ``` + + You can also add this command as an automated deployment step in your CI/CD pipeline by creating a new GitHub action. + + Add the `TRIGGER_ACCESS_TOKEN` secret to your repository secrets, which you can create in the trigger.dev dashboard. + + ```yml title=".github/workflows/deploy-tasks.yml" + name: Deploy to trigger.dev (prod) + + on: + push: + branches: + - main + + jobs: + deploy: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: lts/* + - uses: pnpm/action-setup@v4 + - name: Install dependencies + run: pnpm install + - name: Deploy trigger tasks + env: + TRIGGER_ACCESS_TOKEN: ${{ secrets.TRIGGER_ACCESS_TOKEN }} + run: | + pnpm --filter @turbostarter/tasks deploy + ``` + + + + ## Triggering tasks + + You can trigger tasks from your TurboStarter application using the API layer. + + + While you can trigger tasks directly from your frontend or server components using the trigger.dev SDK, it's recommended to use the API layer approach shown below. + + This provides better security, validation, and separation of concerns. + + + First, add the `@turbostarter/tasks` package as a dependency to your API package: + + ```json title="packages/api/package.json" + { + "dependencies": { + "@turbostarter/tasks": "workspace:*" + } + } + ``` + + ### From an API endpoint + + Create a new API module to handle task triggering: + + ```ts title="packages/api/src/modules/tasks/tasks.router.ts" + import { tasks } from "@trigger.dev/sdk"; + import { Hono } from "hono"; + import * as z from "zod"; + import type { processUserDataTask } from "@turbostarter/tasks"; + + import { enforceAuth, validate } from "../../middleware"; + + const processUserDataSchema = z.object({ + userId: z.string(), + operation: z.enum(["export", "analyze", "cleanup"]), + }); + + export const tasksRouter = new Hono().post( + "/process-user-data", + enforceAuth, + validate("json", processUserDataSchema), + async (c) => { + const { userId, operation } = c.req.valid("json"); + + const handle = await tasks.trigger( + "process-user-data", + { userId, operation }, + ); + + return c.json({ + success: true, + taskId: handle.id, + message: "Background task started successfully", + }); + }, + ); + ``` + + Then register it in your main API router: + + ```ts title="packages/api/src/index.ts" + import { tasksRouter } from "./modules/tasks/tasks.router"; + + const appRouter = new Hono() + .basePath("/api") + .route("/tasks", tasksRouter) + // ... other existing routers + .onError(onError); + + export { appRouter }; + ``` + + ### From the client + + You can call the task endpoint from your web app using TurboStarter's API client: + + ```tsx title="apps/web/src/modules/tasks/process-data-button.tsx" + "use client"; + + import { handle } from "@turbostarter/api/utils"; + import { useMutation } from "@tanstack/react-query"; + + import { api } from "~/lib/api/client"; + + export function ProcessDataButton({ userId }: { userId: string }) { + const { mutate: processData, isPending } = useMutation({ + mutationFn: handle(api.tasks["process-user-data"].$post), + onSuccess: (data) => { + console.log("Task started:", data.taskId); + }, + }); + + return ( + + ); + } + ``` + + ### From a server action + + ```ts title="apps/web/src/app/actions/user-actions.ts" + "use server"; + + import { handle } from "@turbostarter/api/utils"; + + import { api } from "~/lib/api/server"; + + export async function processUserData(userId: string, operation: string) { + try { + const result = await handle(api.tasks["process-user-data"].$post)({ + json: { userId, operation }, + }); + + return { + success: true, + taskId: result.taskId, + }; + } catch (error) { + console.error("Failed to trigger background task:", error); + throw new Error("Failed to start background task"); + } + } + ``` + + + +## Monitoring and debugging + +### Dashboard access + +Visit the [trigger.dev dashboard](https://trigger.dev) to monitor your tasks: + +* View task execution logs and performance metrics +* Track success and failure rates across all your tasks +* Monitor task duration and resource usage +* Replay failed tasks with a single click +* Set up alerts for task failures or performance issues + +### Local development + +During development, run your tasks locally while connected to trigger.dev: + +```bash +# Start everything in the workspace +pnpm dev + +# or start the tasks package only +pnpm --filter @turbostarter/tasks dev +``` + +This allows you to: + +* Test tasks locally with real data +* Debug with breakpoints and console logs +* See immediate feedback as you develop + +## Best practices + + + + ```ts + // ✅ Good - Clear and descriptive + id: "user-data-export-csv"; + id: "weekly-newsletter-campaign"; + id: "cleanup-temp-files"; + + // ❌ Not so good - Generic and unclear + id: "task1"; + id: "job"; + id: "process"; + ``` + + + + ```ts + run: async (payload) => { + try { + const result = await processData(payload); + logger.info("Task completed successfully", { result }); + return result; + } catch (error) { + logger.error("Task failed:", error.message); + throw error; // Re-throw to trigger retry logic + } + }, + ``` + + + + ```ts + logger.info("Processing started", { + userId: payload.userId, + operation: payload.operation, + timestamp: new Date().toISOString(), + }); + ``` + + + + Instead of one massive task, create focused, single-purpose tasks that can be composed together for complex workflows. + + + + Set retry policies based on your task's requirements: + + ```ts + // For critical operations + retry: { + maxAttempts: 5, + minTimeoutInMs: 2000, + maxTimeoutInMs: 30000, + factor: 2, + } + + // For less critical operations + retry: { + maxAttempts: 2, + minTimeoutInMs: 1000, + maxTimeoutInMs: 5000, + factor: 1.5, + } + ``` + + + +## Next steps + +With trigger.dev integrated into your TurboStarter application, you can now: + +* **Handle long-running operations** that would timeout in serverless functions +* **Schedule recurring tasks** like reports, cleanups, and maintenance +* **Process background jobs** reliably with automatic retries +* **Scale your application** without worrying about task execution infrastructure + +Ready to explore more advanced features? Check out the official documentation for additional capabilities like webhooks, batching, and custom integrations. + + + + + + diff --git a/.context/turbostarter-framework-context/sections/web/billing/configuration.md b/.context/turbostarter-framework-context/sections/web/billing/configuration.md new file mode 100644 index 0000000..5cab587 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/billing/configuration.md @@ -0,0 +1,290 @@ +--- +title: Configuration +description: Configure billing for your application. +url: /docs/web/billing/configuration +--- + +# Configuration + +The billing configuration schema replicates your billing provider's schema, so that: + +* we can display the data in the UI (pricing table, billing section, etc.) +* create the correct checkout session +* make some features work correctly - such as feature-based access + +It is common to all billing providers and placed in `packages/billing/src/config/index.ts`. Some billing providers have some differences in what you can or cannot do. In these cases, the schema will try to validate and enforce the rules - but it's up to you to make sure the data is correct. + +The schema is based on few entities: + +* **Plans:** The main product you are selling (e.g., "Pro Plan", "Starter Plan", etc.) +* **Prices:** The pricing plan for the product (e.g., "Monthly", "Yearly", etc.) +* **Discounts:** The discount for the price (e.g., "10% off", "20% off", etc.) + +```ts title="index.ts" +type BillingConfig = { + plans: PlanWithPrices[]; + discounts: Discount[]; +}; +``` + + + Getting the IDs of your plans is **extremely important** - as these are used to: + + * create the correct checkout + * manage your customers billing data + + Please take it easy while you configure this, do one step at a time, and test it thoroughly. + + +## Billing provider + +To set the billing provider, you need to modify the exports in the `packages/billing/src/providers` directory. It defaults to [Stripe](/docs/web/billing/stripe). + + + + ```ts + // [!code word:stripe] + export * from "./stripe"; + ``` + + + + ```ts + // [!code word:stripe] + export * from "./stripe/env"; + ``` + + + +It's important to set it correctly, as this is used to determine the correct API calls and environment variables used during the communication with the billing provider. + +## Billing model + +To set the billing model, you need to modify the `BILLING_MODEL` environment variable. It defaults to `recurring` as it's the most common model for SaaS apps. + +```dotenv +BILLING_MODEL="recurring" +``` + +This field will be used to display corresponding data in the UI (e.g. in pricing tables) and to create the correct checkout session. + + + For now, TurboStarter supports two billing models: + + * `recurring` - for subscription-based models + * `one-time` - for one-time payments + + When changing it, make sure to also update corresponding data on the provider side to match it with the correct billing model. + + +## Plans + +Plans are the main products you are selling. They are defined by the following fields: + +```ts title="index.ts" +export const config = billingConfigSchema.parse({ + ... + plans: [ + { + id: PricingPlanType.PREMIUM, + name: "Premium", + description: "Become a power user and gain benefits", + badge: "Bestseller", + prices: [], + }, + ], + ... +}) satisfies BillingConfig; +``` + +Let's break down the fields: + +* `id`: The unique identifier for the plan (e.g., `free`, `pro`, `enterprise`, etc.). **This is chosen by you, it doesn't need to be the same one as the one in the provider.** It's also used to determine the access level of the plan. +* `name`: The name of the plan +* `description`: The description of the plan +* `badge`: A badge to display on the product (e.g., "Bestseller", "Popular", etc.) + +The majority of these fields are going to populate the pricing table in the UI. + +### Prices + +Prices are the pricing plans for the plan. They are defined by the following fields: + +```ts title="index.ts" +export const config = billingConfigSchema.parse({ + ... + plans: [ + { + id: PricingPlanType.PREMIUM, + name: "Premium", + description: "Become a power user and gain benefits", + badge: "Bestseller", + prices: [ + { + /* 👇 This is the `priceId` from the provider (e.g. Stripe), `variantId` (e.g. Lemon Squeezy) or `productId` (e.g. Polar) */ + id: "price_1PpZAAFQH4McJDTlig6Fxsyy", + amount: 1900, + currency: "usd", + interval: RecurringInterval.MONTH, + trialDays: 7, + type: BillingModel.RECURRING, + }, + ], + }, + ], + ... +}) satisfies BillingConfig; +``` + +Let's break down the fields: + +* `id`: The unique identifier for the price. **This must match the price ID in the billing provider** +* `amount`: The amount of the price (displayed values will be divided by 100) +* `currency`: The currency of the price (only currencies from the [current locale](/docs/web/internationalization/overview) will be displayed - defaults to `usd`) + + + Make sure to have the same currency set on your third-party billing provider (e.g. as a [store currency](https://docs.lemonsqueezy.com/help/payments/currencies) on Lemon Squeezy) + + +* `interval`: The interval of the price (e.g., `month`, `year`, etc.) +* `trialDays`: The number of trial days for the price +* `type`: The type of the price (e.g., `recurring`, `one-time`, etc.) + +The amount is set for UI purposes. The billing provider will handle the actual billing - therefore, please make sure the amount is correctly set in the billing provider. + + + Make sure to set the correct price ID that corresponds to the price in the billing provider. This is very important - as this is used to identify the correct price when creating a checkout session. + + +### One-off payments + +One-off payments are a type of price that is used to create a checkout session for a one-time payment. They are defined by the following fields: + +```ts title="index.ts" +export const config = billingConfigSchema.parse({ + ... + plans: [ + { + id: PricingPlanType.PREMIUM, + name: "Premium", + description: "Become a power user and gain benefits", + badge: "Bestseller", + prices: [ + { + /* 👇 This is the `priceId` from the provider (e.g. Stripe), `variantId` (e.g. Lemon Squeezy) or `productId` (e.g. Polar) */ + id: "price_1PpUagFQH4McJDTlHCzOmyT6", + amount: 29900, + currency: "usd", + type: BillingModel.ONE_TIME, + }, + ], + }, + ], + ... +}) satisfies BillingConfig; +``` + +Let's break down the fields: + +* `id`: The unique identifier for the price. **This must match the price ID in the billing provider** +* `amount`: The amount of the price (displayed values will be divided by 100) +* `currency`: The currency of the price (only currencies from the [current locale](/docs/web/internationalization/overview) will be displayed - defaults to `usd`) +* `type`: The type of the price (e.g. `recurring`, `one-time`, etc.). In this case it's `one-time` as it's a one-off payment. + +Please remember that the cost is set for UI purposes. **The billing provider will handle the actual billing - therefore, please make sure the cost is correctly set in the billing provider.** + +### Custom prices + +Sometimes - you want to display a price in the pricing table - but not actually have it in the billing provider. This is common for custom plans, free plans that don't require the billing provider subscription, or plans that are not yet available. + +To do so, let's add the `custom` flag to the price: + +```ts title="index.ts" +{ + id: "enterprise-monthly", + label: "Contact us!", + href: "/contact", + interval: RecurringInterval.MONTH, + custom: true, + type: BillingModel.RECURRING, +} +``` + +Here's the full example: + +```ts title="index.ts" +export const config = billingConfigSchema.parse({ + ... + plans: [ + { + id: PricingPlanType.PREMIUM, + name: "Premium", + description: "Become a power user and gain benefits", + badge: "Bestseller", + prices: [ + { + id: "premium-monthly", + label: "Contact us!", + href: "/contact", + interval: RecurringInterval.MONTH, + custom: true, + type: BillingModel.RECURRING, + }, + ], + }, + ], + ... +}) satisfies BillingConfig; +``` + +As you can see, the plan is now a custom plan. The UI will display the plan in the pricing table, but it won't be available for purchase. + +We do this by adding the following fields: + +* `custom`: A flag to indicate that the plan is custom. This will prevent the plan from being available for purchase. It's set to `false` by default. +* `label`: This is used to display the label in the pricing table instead of the price. +* `href`: The link to the page where the user can contact you. This is used in the pricing table. + + + All labels and descriptions can be translated using the [internationalization](/docs/web/internationalization/overview) feature. The UI will display the correct translation based on the user's locale. + + ```ts title="index.ts" + label: "common:contactUs", + ``` + + To make strings translatable, make sure to provide the translation key in the config. + + +### Discounts + +Sometimes, you want to offer a discount to your users. This is done by adding a discount to the price in `discounts` field. + +```ts title="index.ts" +export const config = billingConfigSchema.parse({ + ... + discounts: [ + { + code: "50OFF", + type: BillingDiscountType.PERCENT, + off: 50, + appliesTo: [ + "price_1PpUagFQH4McJDTlHwsCzOmyT6", + ], + }, + ], + ... +}) satisfies BillingConfig; +``` + +Let's break down the fields: + +* `code`: The code of the discount (e.g., "50OFF", "10% off", etc.) **This must match the code configured in the billing provider** +* `type`: The type of the discount (e.g., `percent`, `amount`, etc.) +* `off`: The amount of the discount (e.g., 50 for 50% off) +* `appliesTo`: The list of prices that the discount applies to. This is the price ID that you've configured above for the price. + +This data will allow to display the correct banner in the UI e.g. "10% off for the first 100 customers!" and to apply the discount to the correct price at checkout. + +## Adding more products, plans and discounts + +Simply add more plans, prices and discounts to the arrays. The UI **should** be able to handle it in most traditional cases. If you have a more complex billing schema, you may need to adjust the UI accordingly. diff --git a/.context/turbostarter-framework-context/sections/web/billing/creem.md b/.context/turbostarter-framework-context/sections/web/billing/creem.md new file mode 100644 index 0000000..3f3d467 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/billing/creem.md @@ -0,0 +1,13 @@ +--- +title: Creem +description: Manage your customers data and subscriptions using Creem. +url: /docs/web/billing/creem +--- + +# Creem + + + We are working on adding [Creem](https://www.creem.io/) integration to our platform. As soon as it's ready, we will update this page with the necessary information. + + [See roadmap](https://github.com/orgs/turbostarter/projects/1) + diff --git a/.context/turbostarter-framework-context/sections/web/billing/lemon-squeezy.md b/.context/turbostarter-framework-context/sections/web/billing/lemon-squeezy.md new file mode 100644 index 0000000..959b0ed --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/billing/lemon-squeezy.md @@ -0,0 +1,160 @@ +--- +title: Lemon Squeezy +description: Manage your customers data and subscriptions using Lemon Squeezy. +url: /docs/web/billing/lemon-squeezy +--- + +# Lemon Squeezy + +[Lemon Squeezy](https://lemonsqueezy.com/) is another billing provider available within TurboStarter. Here we'll go through the configuration and how to set it up as a provider for your app. + +To switch to Lemon Squeezy, you need to update the exports in: + + + + ```ts + // [!code word:lemon-squeezy] + export * from "./lemon-squeezy"; + ``` + + + + ```ts + // [!code word:lemon-squeezy] + export * from "./lemon-squeezy/env"; + ``` + + + +Then, let's configure the integration: + + + + ## Get API keys + + After you have created your account and a store for [Lemon Squeezy](https://lemonsqueezy.com/), you will need to create a new API key. You can do this by going to the [API page](https://app.lemonsqueezy.com/settings/api) in the settings and clicking on the plus button. You will need to give your API key a name and then click on the *Create* button. Once you have created your API key, you will need to copy the API key to use it in the setup of the integration. + + For local development, make sure to use [Test Mode](https://docs.lemonsqueezy.com/help/getting-started/test-mode) to not mess with the real transactions. + + + + ## Set environment variables + + You need to set the following environment variables: + + ```dotenv title="apps/web/.env.local" + LEMONSQUEEZY_API_KEY="" # Your Lemon Squeezy API key + LEMONSQUEEZY_SIGNING_SECRET="" # Your Lemon Squeezy webhook signing secret + LEMONSQUEEZY_STORE_ID="" # Your Lemon Squeezy store ID (can be found under Settings > Stores next to your store url, e.g #12345) + ``` + + **Please do not add the secret keys to the .env file in production.** During development, you can place them in `.env.local` as it's not committed to the repository. In production, you can set them in the environment variables of your hosting provider. + + + + ## Create products + + For your users to choose from the available subscription plans, you need to create those Products first on the [Products page](https://app.lemonsqueezy.com/products). You can create as many products as you want. + + Create one product per plan you want to offer. You can add multiple variant within the product to offer multiple models or different billing intervals. + + ![Lemon Squeezy Products](/images/docs/web/billing/lemon-squeezy/products.webp) + + To offer multiple intervals for each plan, you can use the [Variant](https://docs.lemonsqueezy.com/help/products/variants) feature of Lemon Squeezy. Just create one variant for each interval/model you want to offer. + + ![Lemon Squeezy Variants](/images/docs/web/billing/lemon-squeezy/variants.png) + + + You need to make sure that the price ID you set in the configuration matches the ID of the variant you created in Lemon Squeezy. + + [See configuration](/docs/web/billing/configuration#prices) for more information. + + + + + ## Create a webhook + + To sync the current subscription status or checkout conclusion and other information to your database, you need to set up a webhook. + + The webhook handling code comes ready to use with TurboStarter, you just have to create the webhook in the Lemon Squeezy dashboard and insert the URL for your project. + + To configure a new webhook, go to the [Webhooks page](https://app.lemonsqueezy.com/settings/webhooks) in the Lemon Squeezy settings and click the *Plus* button. + + ![Lemon Squeezy Webhook](/images/docs/web/billing/lemon-squeezy/webhook.png) + + Select the following events: + + * For subscriptions: + * `subscription_created` + * `subscription_updated` + * `subscription_cancelled` + * For one-off payments: + * `order_created` + + You will also have to enter a *Signing secret* which you can get by running the following command in your terminal: + + ```bash + openssl rand -base64 32 + ``` + + Copy the generated string and paste it into the *Signing secret* field. + + You also need to add this secret to your environment variables: + + ```dotenv title="apps/web/.env.local" + LEMONSQUEEZY_WEBHOOK_SECRET=your-signing-secret + ``` + + To get the callback URL for the webhook, you can either use a local development URL or the URL of your deployed app: + + ### Local development + + If you want to test the webhook locally, you can use [ngrok](https://ngrok.com) to create a tunnel to your local machine. Ngrok will then give you a URL that you can use to test the webhook locally. + + To do so, install ngrok and run it with the following command (while your TurboStarter web development server is running): + + ```bash + ngrok http 3000 + ``` + + ![Ngrok](/images/docs/web/billing/stripe/ngrok.png) + + This will give you a URL (see the *Forwarding* output) that you can use to create a webhook in Lemon Squeezy. Just use that url and add `/api/billing/webhook` to it. + + + + ### Production deployment + + When going to production, you will need to set the webhook URL and the events you want to listen to in Lemon Squeezy. + + The webhook path is `/api/billing/webhook`. If your app is hosted at `https://myapp.com` then you need to enter `https://myapp.com/api/billing/webhook` as the URL. + + All the relevant events are automatically handled by TurboStarter, so you don't need to do anything else. If you want to handle more events please check [Webhooks](/docs/web/billing/webhooks) for more information. + + + +## Add discount + +You can add a discount for your customers that will apply on a specific price. + +You can create the discount on [Discounts page](https://app.lemonsqueezy.com/discounts). + +![Lemon Squeezy Discounts](/images/docs/web/billing/lemon-squeezy/discount.png) + +You can set there a details of discount such as products that it should apply to, amount off, duration, max redemptions and more. + + + +You need to add also the discount code and details to TurboStarter billing configuration to enable displaying it in the UI, creating checkout sessions with it and calculate prices. + +[See discounts configuration](/docs/web/billing/configuration#discounts) for more details. + +That's it! 🎉 You have now set up Lemon Squeezy as a billing provider for your app. + +Feel free to add more products, prices, discounts and manage your customers data and subscriptions using Lemon Squeezy. + + + Make sure that the data you set in the configuration matches the details of things you created in Lemon Squeezy. + + [See configuration](/docs/web/billing/configuration) for more information. + diff --git a/.context/turbostarter-framework-context/sections/web/billing/overview.md b/.context/turbostarter-framework-context/sections/web/billing/overview.md new file mode 100644 index 0000000..b92a736 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/billing/overview.md @@ -0,0 +1,38 @@ +--- +title: Overview +description: Get started with billing in TurboStarter. +url: /docs/web/billing/overview +--- + +# Overview + +The `@turbostarter/billing` package is used to manage subscriptions, one-off payments, and more. + +Inside, we're making an abstraction layer that allows us to use different billing providers without breaking our code nor changing the API calls. + +![Billing Providers](/images/docs/billing-providers.webp) + +## Providers + +TurboStarter implements multiple providers for managing billing: + +* [Stripe](/docs/web/billing/stripe) +* [Lemon Squeezy](/docs/web/billing/lemon-squeezy) +* [Polar](/docs/web/billing/polar) +* [Creem](/docs/web/billing/creem) (coming soon) + +All configuration and setup is built-in with a unified API, so you can switch between providers by simply changing the exports and even introduce your own provider without breaking any billing-related logic. + +## Subscriptions vs. One-off payments + +TurboStarter supports both one-off payments and subscriptions. You have the choice to use one or both. What TurboStarter cannot assume with certainty is the billing mode you want to use. By default, we assume you want to use subscriptions, as this is the most common billing mode for SaaS applications. + +This means that - by default - TurboStarter will be looking for a subscription plan when visiting the billing section or pricing page. + +**It's easily customizable** - [take a look at configuration](/docs/web/billing/configuration). + +### But I want to use both + +Perfect - you can, but you need to customize the pages to display the correct data. + +Depending on the service you use, you will need to set the environment variables accordingly. By default - the billing package uses [Stripe](/docs/web/billing/stripe). Alternatively, you can use [Lemon Squeezy](/docs/web/billing/lemon-squeezy) or [Polar](/docs/web/billing/polar). In the future, we will also add [Creem](/docs/web/billing/creem). diff --git a/.context/turbostarter-framework-context/sections/web/billing/polar.md b/.context/turbostarter-framework-context/sections/web/billing/polar.md new file mode 100644 index 0000000..358d7f4 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/billing/polar.md @@ -0,0 +1,167 @@ +--- +title: Polar +description: Manage your customers data and subscriptions using Polar. +url: /docs/web/billing/polar +--- + +# Polar + +[Polar](https://www.polar.com/) is another billing provider available within TurboStarter. Here we'll go through the configuration and how to set it up as a provider for your app. + +To switch to Polar, you need to update the exports in: + + + + ```ts + // [!code word:polar] + export * from "./polar"; + ``` + + + + ```ts + // [!code word:polar] + export * from "./polar/env"; + ``` + + + +Then, let's configure the integration: + + + + ## Get the access token + + After you have created your account for [Polar](https://www.polar.com/) and created your store, you will need to get the API key. + + Under the *Settings*, scroll to *Developers* and click "New token". Enter a name for the token, set the expiration duration and select the scopes you want the token to have. + + To keep it simple, you can select all scopes. + + ![Polar Access Token](/images/docs/web/billing/polar/access-token.png) + + For local development, make sure to use [Sandbox Mode](https://docs.polar.sh/integrate/sandbox) to not mess with the real transactions. + + + + ## Set environment variables + + You need to set the following environment variables: + + ```dotenv title="apps/web/.env.local" + POLAR_ACCESS_TOKEN="" # Your Polar access token + POLAR_WEBHOOK_SECRET="" # Your Polar webhook secret + POLAR_ORGANIZATION_SLUG="" # Your Polar organization slug (can be found under Settings > Organization) + ``` + + **Please do not add the secret keys to the .env file in production.** During development, you can place them in `.env.local` as it's not committed to the repository. In production, you can set them in the environment variables of your hosting provider. + + + + ## Create products + + For your users to choose from the available subscription plans, you need to create those Products first on the [Products page](https://docs.polar.sh/features/products). You can create as many products as you want. + + ![Polar Products](/images/docs/web/billing/polar/products.png) + + Polar takes a different approach to product variants. Instead of having one product with multiple pricing options, Polar treats each pricing option as a separate product. This simplifies the user experience and API while giving you full flexibility. + + At checkout, customers can choose between different products (like monthly or yearly plans), each with its own pricing and benefits. + + ![Polar Product Variants](/images/docs/web/billing/polar/variants.png) + + + You need to make sure that the price ID you set in the configuration matches the ID of the product you created in Polar. + + [See configuration](/docs/web/billing/configuration#prices) for more information. + + + + + ## Create a webhook + + To sync the current subscription status or checkout conclusion and other information to your database, you need to set up a webhook. + + The webhook handling code comes ready to use with TurboStarter, you just have to create the webhook in the Polar dashboard and insert the URL for your project. + + To configure a new webhook, go to the [Webhooks page](https://docs.polar.sh/integrate/webhooks/endpoints) in the Polar settings and click the *Add endpoint* button. + + ![Polar Webhook](/images/docs/web/billing/polar/webhook.png) + + Select the following events: + + * For subscriptions: + * `subscription.created` + * `subscription.updated` + * `subscription.canceled` + * `subscription.revoked` + * For one-off payments: + * `order.created` + + You will also have to enter a *Secret* which you can get by running the following command in your terminal: + + ```bash + openssl rand -base64 32 + ``` + + Copy the generated string and paste it into the *Secret* field. + + You also need to add this secret to your environment variables: + + ```dotenv title="apps/web/.env.local" + POLAR_WEBHOOK_SECRET=your-generated-secret + ``` + + To get the URL for the webhook, you can either use a local development URL or the URL of your deployed app: + + ### Local development + + If you want to test the webhook locally, you can use [ngrok](https://ngrok.com) to create a tunnel to your local machine. Ngrok will then give you a URL that you can use to test the webhook locally. + + To do so, install ngrok and run it with the following command (while your TurboStarter web development server is running): + + ```bash + ngrok http 3000 + ``` + + ![Ngrok](/images/docs/web/billing/stripe/ngrok.png) + + This will give you a URL (see the *Forwarding* output) that you can use to create a webhook in Polar. Just use that url and add `/api/billing/webhook` to it. + + + + ### Production deployment + + When going to production, you will need to set the webhook URL and the events you want to listen to in Polar. + + The webhook path is `/api/billing/webhook`. If your app is hosted at `https://myapp.com` then you need to enter `https://myapp.com/api/billing/webhook` as the URL. + + All the relevant events are automatically handled by TurboStarter, so you don't need to do anything else. If you want to handle more events please check [Webhooks](/docs/web/billing/webhooks) for more information. + + + +## Add discount + +You can add a discount for your customers that will apply on a specific price. + +You can create the discount under the *Products* page on *Discounts* tab in the Polar dashboard. + +![Polar Discount](/images/docs/web/billing/polar/discount.png) + +You can set there a details of discount such as products that it should apply to, amount off, duration, max redemptions and more. + + + +You need to add also the discount code and details to TurboStarter billing configuration to enable displaying it in the UI, creating checkout sessions with it and calculate prices. + +[See discounts configuration](/docs/web/billing/configuration#discounts) for more details. + +That's it! 🎉 You have now set up Polar as a billing provider for your app. + +Feel free to add more products, prices, discounts and manage your customers data and subscriptions using Polar. + + + Make sure that the data you set in the configuration matches the details of things you created in Polar. + + [See configuration](/docs/web/billing/configuration) for more information. + diff --git a/.context/turbostarter-framework-context/sections/web/billing/stripe.md b/.context/turbostarter-framework-context/sections/web/billing/stripe.md new file mode 100644 index 0000000..ad40a1e --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/billing/stripe.md @@ -0,0 +1,205 @@ +--- +title: Stripe +description: Manage your customers data and subscriptions using Stripe. +url: /docs/web/billing/stripe +--- + +# Stripe + +[Stripe](https://stripe.com) is the default billing provider for TurboStarter. Here we'll go through the configuration and how to set it up as a provider for your app. + + + + ## Get API keys + + After you have created your account for [Stripe](https://stripe.com), you will need to get the API key. You can do this by going to the [API page](https://dashboard.stripe.com/apikeys) in the dashboard. Here you will find the *Secret key* and the *Publishable key*. You will need the *Secret key* for the integration to work. + + For local development, make sure to use [Test Mode](https://docs.stripe.com/test-mode) to not mess with the real transactions. + + + + ## Set environment variables + + You need to set the following environment variables: + + ```dotenv title="apps/web/.env.local" + STRIPE_SECRET_KEY="" # Your Stripe secret key + STRIPE_WEBHOOK_SECRET="" # The secret key of the webhook you created (see below) + ``` + + **Please do not add the secret keys to the .env file in production.** During development, you can place them in `.env.local` as it's not committed to the repository. In production, you can set them in the environment variables of your hosting provider. + + + + ## Create products + + For your users to choose from the available subscription plans, you need to create those Products first on the [Products page](https://dashboard.stripe.com/products). You can create as many products as you want. + + Create one product per plan you want to offer. You can add multiple prices within this product to offer multiple models or different billing intervals. + + ![Stripe Products](/images/docs/web/billing/stripe/products.webp) + + + You need to make sure that the price ID you set in the configuration matches the ID of the price you created in Stripe. + + [See configuration](/docs/web/billing/configuration) for more information. + + + + + ## Create a webhook + + To sync the current subscription status or checkout conclusion and other information to your database, you need to set up a webhook. + + The webhook code comes ready to use with TurboStarter, you just have to create the webhook in the Stripe dashboard and insert the URL for your project. + + To configure a new webhook, go to the [Webhooks page](https://dashboard.stripe.com/webhooks) in the Stripe settings and click the Add endpoint button. + + ![Stripe Webhook](/images/docs/web/billing/stripe/webhook.png) + + Select the following events: + + * For subscriptions: + * `customer.subscription.created` + * `customer.subscription.updated` + * `customer.subscription.deleted` + * For one-off payments: + * `checkout.session.completed` + + To get the URL for the webhook, you can either use a local development URL or the URL of your deployed app: + + ### Local development + + There are two ways to test the webhook during local development: + + + + The Stripe CLI which allows you to listen to Stripe events straight to your own localhost. You can install and use the CLI using a variety of methods, but we recommend using official way to do it. + + [Install the Stripe CLI](https://docs.stripe.com/stripe-cli) + + Then - login to your Stripe account using the project you want to run: + + ```bash + stripe login + ``` + + Copy the webhook secret displayed in the terminal and set it as the `STRIPE_WEBHOOK_SECRET` environment variable in your `apps/web/.env.local` file: + + ```dotenv title="apps/web/.env.local" + STRIPE_WEBHOOK_SECRET=*your-secret-key* + ``` + + Now, you can listen to Stripe events running the following command: + + ```bash + stripe listen --forward-to localhost:3000/api/billing/webhook + ``` + + This will forward all the Stripe events to your local endpoint. + + + **If you have not logged in** - the first time you set it up, you are required to sign in. This is a one-time process. Once you sign in, you can use the CLI to listen to Stripe events. + + **Please sign in and then re-run the command.** Now, you can listen to Stripe events. + + If you're not receiving events, please make sure that: + + * the webhook secret is correct + * the account you signed in is the same as the one you're using in your app + + + You can even trigger the event manually for testing purposes: + + ```bash + stripe trigger customer.subscription.created + ``` + + + + + + If you want to test the webhook locally, you can use [ngrok](https://ngrok.com) to create a tunnel to your local machine. Ngrok will then give you a URL that you can use to test the webhook locally. + + To do so, install ngrok and run it with the following command (while your TurboStarter web development server is running): + + ```bash + ngrok http 3000 + ``` + + ![Ngrok](/images/docs/web/billing/stripe/ngrok.png) + + This will give you a URL (see the *Forwarding* output) that you can use to create a webhook in Stripe. Just use that url and add `/api/billing/webhook` to it. + + + + + + ### Production deployment + + When going to production, you will need to set the webhook URL and the events you want to listen to in Stripe. + + The webhook path is `/api/billing/webhook`. If your app is hosted at `https://myapp.com` then you need to enter `https://myapp.com/api/billing/webhook` as the URL. + + All the relevant events are automatically handled by TurboStarter, so you don't need to do anything else. If you want to handle more events please check [Webhooks](/docs/web/billing/webhooks) for more information. + + + + ## Configure Stripe Customer Portal + + Stripe requires you to set up the Customer Portal so that users can manage their billing information, invoices and plan settings from there. + + You can do it [under the following link.](https://dashboard.stripe.com/settings/billing/portal) + + ![Stripe Customer Portal](/images/docs/web/billing/stripe/customer-portal.png) + + 1. Please make sure to enable the setting that lets users switch plans + 2. Configure the behavior of the cancellation according to your needs + + + +## Add discount + +You can add a discount for your customers that will apply on a specific price. + + + + ### Create coupon + + First, you'd need to create a coupon on the [Coupons page](https://dashboard.stripe.com/coupons). + + ![Stripe Coupons](/images/docs/web/billing/stripe/coupon.png) + + You can set there a details of discount such as prices that it should apply to, amount off, duration, max redemptions and more. + + + + ### Add promotion code + + To enable using code during checkout you need to get a promotion code. You can define it on the same page as the coupon and give some user-friendly name to it. + + ![Stripe Promotion Code](/images/docs/web/billing/stripe/promotion-code.png) + + This code will be auto-applied at new checkout sessions. + + + + + + ### Configure discount + + You need to add also the discount code and details to TurboStarter billing configuration to enable displaying it in the UI, creating checkout sessions with it and calculate prices. + + [See discounts configuration](/docs/web/billing/configuration#discounts) for more details. + + + +That's it! 🎉 You have now set up Stripe as a billing provider for your app. + +Feel free to add more products, prices, discounts and manage your customers data and subscriptions using Stripe. + + + Make sure that the data you set in the configuration matches the details of things you created in Stripe. + + [See configuration](/docs/web/billing/configuration) for more information. + diff --git a/.context/turbostarter-framework-context/sections/web/billing/webhooks.md b/.context/turbostarter-framework-context/sections/web/billing/webhooks.md new file mode 100644 index 0000000..7685f27 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/billing/webhooks.md @@ -0,0 +1,41 @@ +--- +title: Webhooks +description: Handle webhooks from your billing provider. +url: /docs/web/billing/webhooks +--- + +# Webhooks + +TurboStarter handles billing webhooks to update customer data based on events received from the billing provider. + +Occasionally, you may need to set up additional webhooks or perform custom actions with webhooks. + +In such cases, you can customize the billing webhook handler in the billing router at `packages/api/src/modules/billing/router.ts`. + +By default, the webhook handler is configured to be as straightforward as possible: + +```ts title="router.ts" +import { webhookHandler } from "@turbostarter/billing/server"; + +export const billingRouter = new Hono().post("/webhook", (c) => + webhookHandler(c.req.raw), +); +``` + +However, you can extend it using the callbacks provided from `@turbostarter/billing` package: + +```ts title="router.ts" +import { webhookHandler } from "@turbostarter/billing/server"; + +export const billingRouter = new Hono().post("/webhook", (c) => + webhookHandler(c.req.raw, { + onCheckoutSessionCompleted: (sessionId) => {}, + onSubscriptionCreated: (subscriptionId) => {}, + onSubscriptionUpdated: (subscriptionId) => {}, + onSubscriptionDeleted: (subscriptionId) => {}, + onEvent: (rawEvent) => {}, + }), +); +``` + +You can provide one or more of the callbacks to handle the events you are interested in. diff --git a/.context/turbostarter-framework-context/sections/web/cli.md b/.context/turbostarter-framework-context/sections/web/cli.md new file mode 100644 index 0000000..952c328 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/cli.md @@ -0,0 +1,92 @@ +--- +title: CLI +description: Start your new project with a single command. +url: /docs/web/cli +--- + +# CLI + + + +To help you get started with TurboStarter **as quickly as possible**, we've developed a [CLI](https://www.npmjs.com/package/@turbostarter/cli) that enables you to create a new project (with all the configuration) in seconds. + +The CLI is a set of commands that will help you create a new project, generate code, and manage your project efficiently. + +Currently, the following action is available: + +* **Starting a new project** - Generate starter code for your project with all necessary configurations in place (billing, database, emails, etc.) + +**The CLI is in beta**, and we're actively working on adding more commands and actions. Soon, the following features will be available: + +* **Translations** - Translate your project, verify translations, and manage them effectively +* **Installing plugins** - Easily install plugins for your project +* **Dynamic code generation** - Generate dynamic code based on your project structure + +## Installation + +You can run commands using `npx`: + +```bash +npx turbostarter +npx @turbostarter/cli@latest +``` + + + If you don't want to install the CLI globally, you can simply replace the examples below with `npx @turbostarter/cli@latest` instead of `turbostarter`. + + This also allows you to always run the latest version of the CLI without having to update it. + + +## Usage + +Running the CLI without any arguments will display the general information about the CLI: + +```bash +Usage: turbostarter [options] [command] + +Your Turbo Assistant for starting new projects, adding plugins and more. + +Options: + -v, --version display the version number + -h, --help display help for command + +Commands: + new create a new TurboStarter project + help [command] display help for command +``` + +You can also display help for it or check the actual version. + +### Starting a new project + +Use the `new` command to initialize configuration and dependencies for a new project. + +```bash +npx turbostarter new +``` + +You will be asked a few questions to configure your project: + +```bash +✔ All prerequisites are satisfied, let's start! 🚀 + +? What do you want to ship? › + ◉ Web app + ◉ Mobile app + ◯ Browser extension +? Enter your project name. › +? How do you want to use database? › + Local (powered by Docker) + Cloud +? What do you want to use for billing? › + Stripe + Lemon Squeezy + +... + +🎉 You can now get started. Open the project and just ship it! 🎉 + +Problems? https://www.turbostarter.dev/docs +``` + +It will create a new project, configure providers, install dependencies and start required services in development mode. diff --git a/.context/turbostarter-framework-context/sections/web/cms/blog.md b/.context/turbostarter-framework-context/sections/web/cms/blog.md new file mode 100644 index 0000000..aa35bc6 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/cms/blog.md @@ -0,0 +1,86 @@ +--- +title: Blog +description: Learn how to manage your blog content. +url: /docs/web/cms/blog +--- + +# Blog + +TurboStarter comes with a pre-configured blog implementation that allows you to manage your blog content. + +## Creating a new blog post + +To create a new blog post, you need to create a new directory (its name will be used as the slug of the blog post) with `.mdx` files in the `packages/cms/src/collections/blog/content` directory. Each file in this directory should be named after the locale it belongs to (e.g `en.mdx`, `es.mdx`, etc.). + +The file will start with a [frontmatter](https://mdxjs.com/guides/frontmatter/) block, which is a yaml-like block that contains metadata about the post. The frontmatter block should be surrounded by three dashes (`---`). + +```mdx title="packages/cms/src/collections/blog/content/my-first-blog-post/en.mdx" +--- +title: Quick Tips to Improve Your Skills Right Away +description: Whether you're learning a new technical skill or working on personal development, these quick tips can help you improve right away. Learn how to break down your goals, practice consistently, and track your progress using Markdown. +publishedAt: 2023-04-19 +tags: [learning, skills, progress] +thumbnail: https://images.unsplash.com/photo-1483639130939-150975af84e5?q=80&w=2370&auto=format&fit=crop&ixlib=rb-4.0.3&ixid=M3wxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8fA%3D%3D +status: published +--- +``` + +Let's break down the frontmatter fields: + +* `title`: The title of the blog post (it will be also used to generate a slug for the blog post) +* `description`: The description of the blog post +* `publishedAt`: The date when the blog post was published +* `tags`: The tags of the blog post +* `thumbnail`: The thumbnail of the blog post +* `status`: The status of the blog post (could be `published` or `draft`) + +After the frontmatter block, you can add the content of the blog post: + +```mdx title="packages/cms/src/collections/blog/content/my-first-blog-post/en.mdx" +# Quick Tips to Improve Your Skills Right Away + +Awesome paragraph! + +[Link](https://www.turbostarter.dev) + +This is a callout component. + +... +``` + +You can consume the content the same as it's described in [Content Collections](/docs/web/cms/content-collections). + +## BONUS: Using custom components + +As you're using MDX, you can use **any React component** in your blog posts. Just define it as a normal React component and pass it to `` in `components` prop: + +```tsx title="apps/web/src/app/content/page.tsx" +import { MyComponent } from "~/modules/common/my-component"; + +export default function Page() { + return ( + + ); +} +``` + +Then, you would be able to use it in your document content and it will rendered on the page as a result: + +```mdx title="packages/cms/src/collections/blog/content/my-first-blog-post/en.mdx" +... + +# Heading + +Excellent paragraph! + + + +1. First item +2. Second item +3. Third item +``` + +TurboStarter ships with a set of default components that you can use in your blog posts, e.g. ``, `` etc. Use them or define your own to make your blog posts more engaging. diff --git a/.context/turbostarter-framework-context/sections/web/cms/content-collections.md b/.context/turbostarter-framework-context/sections/web/cms/content-collections.md new file mode 100644 index 0000000..a303a2f --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/cms/content-collections.md @@ -0,0 +1,98 @@ +--- +title: Content Collections +description: Get started with Content Collections. +url: /docs/web/cms/content-collections +--- + +# Content Collections + +By default, TurboStarter uses [Content Collections](https://www.content-collections.dev/) to store and retrieve content from the MDX files. + +Content from there is used to populate data in the following places: + +* **Blog** +* **Legal pages** +* **Documentation** + + + It is a great alternative to headless CMS like Contentful or Prismic based on MDX (a more powerful version of markdown). It is free, open source and the content is located right in your repository. + + +Of course, you can add more collections and views, as it's very flexible. + +## Defining new collection + +To define a new collection, you need to create a new file in the `packages/cms/src/collections` directory: + +```ts title="packages/cms/src/collections/legal/index.ts" +import { defineCollection } from "@content-collections/core"; + +export const legal = defineCollection({ + name: "legal", + directory: "src/collections/legal/content", + include: "**/*.mdx", + schema: (z) => ({ + title: z.string(), + description: z.string(), + }), + transform: async (doc, context) => { + const mdx = await transformMDX(doc, context); + + return { + ...mdx, + slug: doc._meta.directory, + locale: doc._meta.fileName.split(".")[0], + }; + }, +}); +``` + +Then it's passed to the config in `packages/cms/content-collections.ts` file which is used to generate types and parse content from MDX files. + +```tsx title="packages/cms/content-collections.ts" +import { defineConfig } from "@content-collections/core"; + +import { legal } from "./src/collections/legal"; + +export default defineConfig({ + collections: [legal], +}); +``` + +When you run a development server, content collections will be automatically rebuilt (in `.content-collections` directory) and you will be able to import the content and metadata of each file in your application. + + + By exporting the generated content you get fully type-safe API to interact + with the content. We can have type safety on the data that we're receiving + from the MDX files. + + +## Using content collections + +To get some content from `@turbostarter/cms` package, you need to use the exposed API that we described in the [Overview section](/docs/web/cms/overview#api): + +```tsx title="apps/web/src/app/[locale](marketing)/legal/[slug]/page.tsx" +import { content } from "@turbostarter/cms"; + +export default async function Page({ + params, +}: { + params: Promise<{ slug: string; locale: string }>; +}) { + const item = getContentItemBySlug({ + collection: CollectionType.LEGAL, + slug: (await params).slug, + locale: (await params).locale, + }); + + return

{title}

; +} +``` + +Voila! You can now access the content from the MDX files. + + + + + + diff --git a/.context/turbostarter-framework-context/sections/web/cms/overview.md b/.context/turbostarter-framework-context/sections/web/cms/overview.md new file mode 100644 index 0000000..73017da --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/cms/overview.md @@ -0,0 +1,67 @@ +--- +title: Overview +description: Manage your content in TurboStarter. +url: /docs/web/cms/overview +--- + +# Overview + +TurboStarter implements a CMS interface that abstracts the implementation from where you store your data. It provides a simple API to interact with your data, and it's easy to extend and customize. + +By default, the starter kit ships with these implementations in place: + +1. [Content Collections](https://www.content-collections.dev/) - a headless CMS that uses [MDX](https://mdxjs.com/) files to store your content. + +The implementation is available under `@turbostarter/cms` package, here we'll go over how to use it. + +## API + +The CMS package provides a simple, unified API to interact with the content. It's the same for all the providers, so you can easily use it with any of the implementations without changing the code. + +### Fetching content items + +To fetch items from your colletions, you can use the `getContentItems` function. + +```ts +import { getContentItems } from "@turbostarter/cms"; + +const { items, count } = getContentItems({ + collection: CollectionType.BLOG, + tags: [ContentTag.SKILLS], + sortBy: "publishedAt", + sortOrder: SortOrder.DESCENDING, + status: ContentStatus.PUBLISHED, + locale: "en", +}); +``` + +It accepts an object with the following properties: + +* `collection`: The collection to fetch the items from. +* `tags`: The tags to filter the items by. +* `sortBy`: The field to sort the items by. +* `sortOrder`: The order to sort the items in. +* `status`: The status of the items to fetch. It can be `published` or `draft`. By default, only `published` items are fetched. +* `locale`: The locale to fetch the items in. By default, all locales are fetched. + +### Fetching a single content item + +To fetch a single content item, you can use the `getContentItemBySlug` function. + +```ts +import { getContentItemBySlug } from "@turbostarter/cms"; + +const item = getContentItemBySlug({ + collection: CollectionType.BLOG, + slug: "my-first-blog-post", + status: ContentStatus.PUBLISHED, + locale: "en", +}); +``` + +It accepts an object with the following properties: + +* `collection`: The collection to fetch the item from. +* `slug`: The slug of the item to fetch. +* `status`: The status of the item to fetch. It can be `published` or `draft`. By default, only `published` items are fetched. +* `locale`: The locale to fetch the item in. By default, all locales are fetched. diff --git a/.context/turbostarter-framework-context/sections/web/configuration/app.md b/.context/turbostarter-framework-context/sections/web/configuration/app.md new file mode 100644 index 0000000..8900f19 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/configuration/app.md @@ -0,0 +1,40 @@ +--- +title: App configuration +description: Learn how to setup the overall settings of your app. +url: /docs/web/configuration/app +--- + +# App configuration + +The application configuration is set at `apps/web/src/config/app.ts`. This configuration stores some overall variables for your application. + +This allows you to host multiple apps in the same monorepo, as every application defines its own configuration. + +The recommendation is to **not update this directly** - instead, please define the environment variables and override the default behavior. The configuration is strongly typed so you can use it safely accross your codebase - it'll be validated at build time. + +```ts title="apps/web/src/config/app.ts" +import env from "env.config"; + +export const appConfig = { + name: env.NEXT_PUBLIC_PRODUCT_NAME, + url: env.NEXT_PUBLIC_URL, + locale: env.NEXT_PUBLIC_DEFAULT_LOCALE, + theme: { + mode: env.NEXT_PUBLIC_THEME_MODE, + color: env.NEXT_PUBLIC_THEME_COLOR, + }, +} as const; +``` + +For example, to set the product name and default locale, you'd update the following variables: + +```dotenv title=".env.local" +NEXT_PUBLIC_PRODUCT_NAME="TurboStarter" +NEXT_PUBLIC_DEFAULT_LOCALE="en" +``` + + + Do NOT use `process.env` to get the values of the variables. Variables + accessed this way are not validated at build time, and thus the wrong variable + can be used in production. + diff --git a/.context/turbostarter-framework-context/sections/web/configuration/environment-variables.md b/.context/turbostarter-framework-context/sections/web/configuration/environment-variables.md new file mode 100644 index 0000000..28dcddb --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/configuration/environment-variables.md @@ -0,0 +1,104 @@ +--- +title: Environment variables +description: Learn how to configure environment variables. +url: /docs/web/configuration/environment-variables +--- + +# Environment variables + +Environment variables are defined in the `.env` file in the root of the repository and in the root of the `apps/web` package. + +* **Shared environment variables**: Defined in the **root** `.env` file. These are shared between environments (e.g., development, staging, production) and apps (e.g., web, mobile). +* **Environment-specific variables**: Defined in `.env.development` and `.env.production` files. These are specific to the development and production environments. +* **App-specific variables**: Defined in the app-specific directory (e.g., `apps/web`). These are specific to the app and are not shared between apps. +* **Secret keys**: Not stored in the `.env` file. Instead, they are stored in the environment variables of the CI/CD system. +* **Local secret keys**: If you need to use secret keys locally, you can store them in the `.env.local` file. This file is not committed to Git, making it safe for sensitive information. + +## Shared variables + +Here you can add all the environment variables that are shared across all the apps. This file should be located in the **root** of the project. + +To override these variables in a specific environment, please add them to the specific environment file (e.g. `.env.development`, `.env.production`). + +```dotenv title=".env.local" +# Shared environment variables + +# The database URL is used to connect to your database. +DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres" + +# The name of the product. This is used in various places across the apps. +PRODUCT_NAME="TurboStarter" + +# The url of the web app. Used mostly to link between apps. +URL="http://localhost:3000" + +... +``` + +If you're using Supabase for your database, the [Supabase recipe](/docs/web/recipes/supabase#configure-environment-variables) shows the exact `DATABASE_URL` format and how to set it in your `.env.local`. + +## App-specific variables + +Here you can add all the environment variables that are specific to the app (e.g. `apps/web`). + +You can also override the shared variables defined in the root `.env` file. + +```dotenv title="apps/web/.env.local" +# App-specific environment variables + +# Env variables extracted from shared to be exposed to the client in Next.js app +NEXT_PUBLIC_PRODUCT_NAME="${PRODUCT_NAME}" +NEXT_PUBLIC_URL="${URL}" +NEXT_PUBLIC_DEFAULT_LOCALE="${DEFAULT_LOCALE}" + +# Theme mode and color +NEXT_PUBLIC_THEME_MODE="system" +NEXT_PUBLIC_THEME_COLOR="orange" + +... +``` + + + To make environment variables available in the Next.js **client-side** app code, you need to prefix them with `NEXT_PUBLIC_`. They will be injected to the code during the build process. + + Only environment variables prefixed with `NEXT_PUBLIC_` will be injected, so don't use this prefix for environment variables that should be used only in the server-side code. + + [Read more about Next.js environment variables.](https://nextjs.org/docs/pages/building-your-application/configuring/environment-variables) + + +## Secret keys + +Secret keys and sensitive information are to be never stored in the `.env` file. Instead, **they are stored in the environment variables of the CI/CD system.** + + + It means that you will need to add the secret keys to the environment + variables of your CI/CD system (e.g., GitHub Actions, Vercel, Cloudflare, your + VPS, Netlify, etc.). This is not a TurboStarter-specific requirement, but a + best practice for security for any application. Ultimately, it's your choice. + + +Below is some examples of "what is a secret key?" in practice. + +```dotenv title=".env.local" +# Secret keys + +# The database URL is used to connect to your database. +DATABASE_URL="postgresql://postgres:postgres@localhost:5432/postgres" + +# Stripe server config - required only if you use Stripe as a billing provider +STRIPE_WEBHOOK_SECRET="" +STRIPE_SECRET_KEY="" + +# Lemon Squeezy server config - required only if you use Lemon Squeezy as a billing provider +LEMON_SQUEEZY_API_KEY="" +LEMON_SQUEEZY_SIGNING_SECRET="" +LEMON_SQUEEZY_STORE_ID="" + +... +``` + + + If you need to use secret keys locally, you can store them in the `.env.local` + file. This file is not committed to Git, therefore it is safe to store + sensitive information in it. + diff --git a/.context/turbostarter-framework-context/sections/web/configuration/paths.md b/.context/turbostarter-framework-context/sections/web/configuration/paths.md new file mode 100644 index 0000000..b12a584 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/configuration/paths.md @@ -0,0 +1,52 @@ +--- +title: Paths configuration +description: Learn how to configure the paths of your app. +url: /docs/web/configuration/paths +--- + +# Paths configuration + +The paths configuration is set at `apps/web/config/paths.ts`. This configuration stores all the paths that you'll be using in your application. It is a convenient way to store them in a central place rather than scatter them in the codebase using magic strings. + +It is **unlikely you'll need to change** this unless you're heavily editing the codebase. + +```ts title="apps/web/config/paths.ts" +const pathsConfig = { + index: "/", + marketing: { + pricing: "/pricing", + contact: "/contact", + blog: { + index: BLOG_PREFIX, + post: (slug: string) => `${BLOG_PREFIX}/${slug}`, + }, + legal: (slug: string) => `${LEGAL_PREFIX}/${slug}`, + }, + auth: { + login: `${AUTH_PREFIX}/login`, + register: `${AUTH_PREFIX}/register`, + join: `${AUTH_PREFIX}/join`, + forgotPassword: `${AUTH_PREFIX}/password/forgot`, + updatePassword: `${AUTH_PREFIX}/password/update`, + error: `${AUTH_PREFIX}/error`, + }, + dashboard: { + user: { + index: DASHBOARD_PREFIX, + ai: `${DASHBOARD_PREFIX}/ai`, + settings: { + index: `${DASHBOARD_PREFIX}/settings`, + security: `${DASHBOARD_PREFIX}/settings/security`, + billing: `${DASHBOARD_PREFIX}/settings/billing`, + }, + }, + ... + }, + ..., +} as const; +``` + + + By declaring the paths as constants, we can use them safely throughout the + codebase. There is no risk of misspelling or using magic strings. + diff --git a/.context/turbostarter-framework-context/sections/web/customization/add-app.md b/.context/turbostarter-framework-context/sections/web/customization/add-app.md new file mode 100644 index 0000000..0976266 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/customization/add-app.md @@ -0,0 +1,83 @@ +--- +title: Adding apps +description: Learn how to add apps to your Turborepo workspace. +url: /docs/web/customization/add-app +--- + +# Adding apps + + + This is an **advanced topic** - you should only follow these instructions if you are sure you want to add a new app to your TurboStarter project within your monorepo and want to keep pulling updates from the TurboStarter repository. + + +In some ways - creating a new repository may be the easiest way to manage your application. However, if you want to keep your application within the monorepo and pull updates from the TurboStarter repository, you can follow these instructions. + +To pull updates into a separate application outside of `web` - we can use [git subtree](https://www.atlassian.com/git/tutorials/git-subtree). + +Basically, we will create a subtree at `apps/web` and create a new remote branch for the subtree. When we create a new application, we will pull the subtree into the new application. This allows us to keep it in sync with the `apps/web` folder. + +To add a new app to your TurboStarter project, you need to follow these steps: + + + + ## Create a subtree + + First, we need to create a subtree for the `apps/web` folder. We will create a branch named `web-branch` and create a subtree for the `apps/web` folder. + + ```bash + git subtree split --prefix=apps/web --branch web-branch + ``` + + + + ## Create a new app + + Now, we can create a new application in the `apps` folder. + + Let's say we want to create a new app `ai-chat` at `apps/ai-chat` with the same structure as the `apps/web` folder (which acts as the template for all new apps). + + ```bash + git subtree add --prefix=apps/ai-chat origin web-branch --squash + ``` + + You should now be able to see the `apps/ai-chat` folder with the contents of the `apps/web` folder. + + + + ## Update the app + + When you want to update the new application, follow these steps: + + ### Pull the latest updates from the TurboStarter repository + + The command below will update all the changes from the TurboStarter repository: + + ```bash + git pull upstream main + ``` + + ### Push the `web-branch` updates + + After you have pulled the updates from the TurboStarter repository, you can split the branch again and push the updates to the web-branch: + + ```bash + git subtree split --prefix=apps/web --branch web-branch + ``` + + Now, you can push the updates to the `web-branch`: + + ```bash + git push origin web-branch + ``` + + ### Pull the updates to the new application + + Now, you can pull the updates to the new application: + + ```bash + git subtree pull --prefix=apps/ai-chat origin web-branch --squash + ``` + + + +That's it! You now have a new application in the monorepo 🎉 diff --git a/.context/turbostarter-framework-context/sections/web/customization/add-package.md b/.context/turbostarter-framework-context/sections/web/customization/add-package.md new file mode 100644 index 0000000..b204c2b --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/customization/add-package.md @@ -0,0 +1,109 @@ +--- +title: Adding packages +description: Learn how to add packages to your Turborepo workspace. +url: /docs/web/customization/add-package +--- + +# Adding packages + + + This is an **advanced topic** - you should only follow these instructions if you are sure you want to add a new package to your TurboStarter application instead of adding a folder to your application in `apps/web` or modify existing packages under `packages`. You don't need to do this to add a new page or component to your application. + + +To add a new package to your TurboStarter application, you need to follow these steps: + + + + ## Generate a new package + + First, enter the command below to create a new package in your TurboStarter application: + + ```bash + turbo gen package + ``` + + Turborepo will ask you to enter the name of the package you want to create. Enter the name of the package you want to create and press enter. + + If you don't want to add dependencies to your package, you can skip this step by pressing enter. + + The command will have generated a new package under packages named `@turbostarter/`. If you named it `example`, the package will be named `@turbostarter/example`. + + Finally, to make fast refresh work when you make changes to the package, you need to add the package to the `next.config.ts` file in the root of your TurboStarter application `apps/web`. + + ```ts title="next.config.ts" + const INTERNAL_PACKAGES = [ + // all internal packages, + "@turbostarter/example", + ]; + ``` + + + + ## Export a module from your package + + By default, the package exports a single module using the `index.ts` file. You can add more exports by creating new files in the package directory and exporting them from the `index.ts` file or creating export files in the package directory and adding them to the `exports` field in the `package.json` file. + + ### From `index.ts` file + + The easiest way to export a module from a package is to create a new file in the package directory and export it from the `index.ts` file. + + ```ts title="packages/example/src/module.ts" + export function example() { + return "example"; + } + ``` + + Then, export the module from the `index.ts` file. + + ```ts title="packages/example/src/index.ts" + export * from "./module"; + ``` + + ### From `exports` field in `package.json` + + **This can be very useful for tree-shaking.** Assuming you have a file named `module.ts` in the package directory, you can export it by adding it to the `exports` field in the `package.json` file. + + ```json title="packages/example/package.json" + { + "exports": { + ".": "./src/index.ts", + "./module": "./src/module.ts" + } + } + ``` + + **When to do this?** + + 1. when exporting two modules that don't share dependencies to ensure better tree-shaking. For example, if your exports contains both client and server modules. + 2. for better organization of your package + + For example, create two exports `client` and `server` in the package directory and add them to the `exports` field in the `package.json` file. + + ```json title="packages/example/package.json" + { + "exports": { + ".": "./src/index.ts", + "./client": "./src/client.ts", + "./server": "./src/server.ts" + } + } + ``` + + 1. The `client` module can be imported using `import { client } from '@turbostarter/example/client'` + 2. The `server` module can be imported using `import { server } from '@turbostarter/example/server'` + + + + ## Use the package in your application + + You can now use the package in your application by importing it using the package name: + + ```ts title="apps/web/src/app/page.tsx" + import { example } from "@turbostarter/example"; + + console.log(example()); + ``` + + + +Et voilà! You have successfully added a new package to your TurboStarter application. 🎉 diff --git a/.context/turbostarter-framework-context/sections/web/customization/components.md b/.context/turbostarter-framework-context/sections/web/customization/components.md new file mode 100644 index 0000000..570b5ce --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/customization/components.md @@ -0,0 +1,120 @@ +--- +title: Components +description: Manage and customize your app components. +url: /docs/web/customization/components +--- + +# Components + +For the components part, we're using [shadcn/ui](https://ui.shadcn.com) for atomic, accessible and highly customizable components. + + + shadcn/ui is a powerful tool that allows you to generate pre-designed + components with a single command. It's built with Tailwind CSS and Radix UI, + and it's highly customizable. + + +TurboStarter defines two packages that are responsible for the UI part of your app: + +* `@turbostarter/ui` - shared styles, [themes](/docs/web/customization/styling#themes) and assets (e.g. icons) +* `@turbostarter/ui-web` - pre-built UI web components, ready to use in your app + +## Adding a new component + +There are basically two ways to add a new component: + + + + TurboStarter is fully compatible with [shadcn CLI](https://ui.shadcn.com/docs/cli), so you can generate new components with single command. + + Run the following command from the **root** of your project: + + ```bash + pnpm --filter @turbostarter/ui-web ui:add + ``` + + This will launch an interactive command-line interface to guide you through the process of adding a new component where you can pick which component you want to add. + + ```bash + Which components would you like to add? > Space to select. A to toggle all. + Enter to submit. + + ◯ accordion + ◯ alert + ◯ alert-dialog + ◯ aspect-ratio + ◯ avatar + ◯ badge + ◯ button + ◯ calendar + ◯ card + ◯ checkbox + ``` + + Newly created components will appear in the `packages/ui/web/src` directory. + + + + You can always copy-paste a component from the [shadcn/ui](https://ui.shadcn.com/docs/components) website and modify it to your needs. + + This is possible, because the components are headless and don't need (in most cases) any additional dependencies. + + Copy code from the website, create a new file in the `packages/ui/web/src` directory and paste the code into the file. + + + + + Keep in mind that you should always try to keep shared components as atomic as possible. This will make it easier to reuse them and to build specific views by composition. + + E.g. include components like `Button`, `Input`, `Card`, `Dialog` in shared package, but keep specific components like `LoginForm` in your app directory. + + +## Using components + +Each component is a standalone entity which has a separate export from the package. It helps to keep things modular, avoid unnecessary dependencies and make tree-shaking possible. + +To import a component from the UI package, use the following syntax: + +```tsx title="components/my-component.tsx" +// [!code word:card] +import { + Card, + CardContent, + CardHeader, + CardFooter, + CardTitle, + CardDescription, +} from "@turbostarter/ui-web/card"; +``` + +Then you can use it to build a component specific to your app: + +```tsx title="components/my-component.tsx" +export function MyComponent() { + return ( + + + My Component + + +

My Component Content

+
+ + + +
+ ); +} +``` + + + We recommend using [v0](https://v0.dev) to generate layouts for your app. It's a powerful tool that allows you to generate layouts from the natural language instructions. + + Of course, **it won't replace a designer**, but it can be a good starting point for your layout. + + + + + + + diff --git a/.context/turbostarter-framework-context/sections/web/customization/styling.md b/.context/turbostarter-framework-context/sections/web/customization/styling.md new file mode 100644 index 0000000..7f0c879 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/customization/styling.md @@ -0,0 +1,153 @@ +--- +title: Styling +description: Get started with styling your app. +url: /docs/web/customization/styling +--- + +# Styling + +To build the web user interface, TurboStarter comes with [Tailwind CSS](https://tailwindcss.com/) and [Radix UI](https://www.radix-ui.com/) pre-configured. + + + The combination of Tailwind CSS and Radix UI gives ready-to-use, accessible UI components that can be fully customized to match your brand's design. + + +## Tailwind configuration + +In the `packages/ui/shared/src/styles` directory, you will find shared CSS files with Tailwind CSS configuration. To change global styles, you can edit the files in this folder. + +Here is an example of a shared CSS file that includes the Tailwind CSS configuration: + +```css title="packages/ui/shared/src/styles/globals.css" +@import "tailwindcss"; +@import "./themes.css"; + +@custom-variant dark (&:is(.dark *)); + +:root { + --radius: 0.65rem; +} + +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --color-popover: var(--popover); + --color-popover-foreground: var(--popover-foreground); + --color-primary: var(--primary); + --color-primary-foreground: var(--primary-foreground); + --color-secondary: var(--secondary); + --color-secondary-foreground: var(--secondary-foreground); + --color-muted: var(--muted); + --color-muted-foreground: var(--muted-foreground); + + ... +} +``` + +For colors, we rely strictly on [CSS Variables](https://developer.mozilla.org/en-US/docs/Web/CSS/Using_CSS_custom_properties) in [OKLCH](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch) format to allow for easy theme management without the need for any JavaScript. + +Also, each app has its own `globals.css` file, which extends the shared config and allows you to override the global styles. + +Here is an example of an app's `globals.css` file: + +```css title="apps/web/src/assets/styles/globals.css" +@import "@turbostarter/ui/globals.css"; +@import "@turbostarter/ui-web/globals.css"; + +@theme inline { + /* Overridden theme variables for the app */ + --background: oklch(0.98 0.01 80); + --foreground: oklch(0.22 0.03 120); + --card: oklch(0.97 0.02 50); + --card-foreground: oklch(0.18 0.01 280); + ... +} +``` + +This way, we maintain a separation of concerns and a clear structure for the Tailwind CSS configuration. + +## Themes + +TurboStarter comes with **9+** predefined themes, which you can use to quickly change the look and feel of your app. + +They're defined in the `packages/ui/shared/src/styles/themes` directory. Each theme is a set of variables that can be overridden: + +```ts title="packages/ui/shared/src/styles/themes/colors/orange.ts" +export const orange = { + light: { + background: [1, 0, 0], + foreground: [0.141, 0.005, 285.823], + card: [1, 0, 0], + "card-foreground": [0.141, 0.005, 285.823], + ... + } +} satisfies ThemeColors; +``` + +Each variable is stored as a [OKLCH](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value/oklch) array, which is then converted to a CSS variable at build time (by our custom build script). That way we can ensure full type-safety and reuse themes across different parts of our apps (e.g. use the same theme in emails). + +Feel free to add your own themes or override the existing ones to match your brand's identity. + +To apply a theme to your app, you can use the `data-theme` attribute on the `html` element: + +```tsx title="apps/web/src/app/layout.tsx" +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + {children} + + ); +} +``` + +## Dark mode + +TurboStarter comes with built-in dark mode support. + +Each theme has a corresponding set of dark mode variables, which are used to switch the theme to its dark mode counterpart. + +```ts title="packages/ui/shared/src/styles/themes/colors/orange.ts" +export const orange = { + light: {}, + dark: { + background: [0.141, 0.005, 285.823], + foreground: [0.985, 0, 0], + card: [0.21, 0.006, 285.885], + "card-foreground": [0.985, 0, 0], + ... + } +} satisfies ThemeColors; +``` + +Because the dark variant is defined to use a class (`@custom-variant dark (&:is(.dark *))`) in the shared Tailwind configuration, we need to add the `dark` class to the `html` element to apply dark mode styles. + +For this purpose, we're using the [next-themes](https://github.com/pacocoursey/next-themes) package under the hood to handle user preference management. + +```tsx title="apps/web/src/providers/theme.tsx" +export const ThemeProvider = memo(({ children }) => { + return ( + + {children} + + + ); +}); +``` + +You can also define the default theme mode and color in the [app configuration](/docs/web/configuration/app). + + + + + + diff --git a/.context/turbostarter-framework-context/sections/web/database/client.md b/.context/turbostarter-framework-context/sections/web/database/client.md new file mode 100644 index 0000000..f01c83e --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/database/client.md @@ -0,0 +1,79 @@ +--- +title: Database client +description: Use database client to interact with the database. +url: /docs/web/database/client +--- + +# Database client + +The database client is an export of the Drizzle client. It is automatically typed by Drizzle based on the schema and is exposed as the db object from the database package (`@turbostarter/db`) in the monorepo. + +This guide covers how to initialize the client and also basic operations, such as querying, creating, updating, and deleting records. To learn more about the Drizzle client, check out the [official documentation](https://orm.drizzle.team/kit-docs/overview). + +## Initializing the client + +Pass the validated `DATABASE_URL` to the client to initialize it. + +```ts title="server.ts" +import { drizzle } from "drizzle-orm/postgres-js"; +import postgres from "postgres"; + +import { env } from "../env"; + +const client = postgres(env.DATABASE_URL); +export const db = drizzle(client); +``` + +Now it's exported from the `@turbostarter/db` package and can be used across the codebase (server-side). + +## Querying data + +To query data, you can use the `db` object and its methods: + +```ts title="query.ts" +import { eq } from "@turbostarter/db"; +import { db } from "@turbostarter/db/server"; +import { customer } from "@turbostarter/db/schema"; + +export const getCustomerByUserId = async (userId: string) => { + const [data] = await db + .select() + .from(customer) + .where(eq(customer.userId, userId)); + + return data ?? null; +}; +``` + + + + + + + + + +## Mutating data + +You can use the exported utilities to mutate data. Insert, update or delete records in fast and fully type-safe way: + +```ts title="mutation.ts" +import { eq } from "@turbostarter/db"; +import { db } from "@turbostarter/db/server"; +import { customer } from "@turbostarter/db/schema"; + +export const upsertCustomer = (data: InsertCustomer) => { + return db.insert(customer).values(data).onConflictDoUpdate({ + target: customer.userId, + set: data, + }); +}; +``` + + + + + + + + diff --git a/.context/turbostarter-framework-context/sections/web/database/migrations.md b/.context/turbostarter-framework-context/sections/web/database/migrations.md new file mode 100644 index 0000000..6686856 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/database/migrations.md @@ -0,0 +1,49 @@ +--- +title: Migrations +description: Migrate your changes to the database. +url: /docs/web/database/migrations +--- + +# Migrations + +You have your schema in place, and you want to apply your changes to the database. TurboStarter provides you a convenient way to do so with pre-configured CLI commands. + +## Generating migration + +To generate a migration, from the schema you need to run the following command: + +```bash +pnpm with-env turbo db:generate +``` + +This will create a new `.sql` file in the `migrations` directory. + + + Drizzle will also generate a `.json` representation of the migration in the `meta` directory, but it's for its internal purposes and you shouldn't need to touch it. + + +## Applying migrations + +To apply the migrations to the database, you need to run the following command: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:migrate +``` + +This will apply all the migrations that have not been applied yet. If any conflicts arise, you can resolve them by modifying the generated migration file. + +## Pushing changes + +To push changes directly to the database, you can use the following command: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:push +``` + +This lets you push your schema changes directly to the database and omit managing SQL migration files. + + + Pushing changes directly to the database (without using migrations) could be risky. Please be careful when using it; we recommend it only for local development and local databases. + + [Read more about it in the Drizzle docs](https://orm.drizzle.team/kit-docs/overview#prototyping-with-db-push). + diff --git a/.context/turbostarter-framework-context/sections/web/database/overview.md b/.context/turbostarter-framework-context/sections/web/database/overview.md new file mode 100644 index 0000000..62d6e42 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/database/overview.md @@ -0,0 +1,83 @@ +--- +title: Overview +description: Get started with the database. +url: /docs/web/database/overview +--- + +# Overview + +We're using [Drizzle ORM](https://orm.drizzle.team) to interact with the database. It basically adds a little layer of abstraction between our code and the database. + +> If you know SQL, you know Drizzle. + +For the database we're leveraging [PostgreSQL](https://www.postgresql.org), but you could use any other database that Drizzle ORM supports (basically any SQL database e.g. [MySQL](https://orm.drizzle.team/docs/get-started-mysql), [SQLite](https://orm.drizzle.team/docs/get-started-sqlite), etc.). + + + Drizzle ORM is a powerful tool that allows you to interact with the database in a type-safe manner. It ships with **0** (!) dependencies and is designed to be fast and easy to use. + + +## Setup + +To start interacting with the database you first need to ensure that your database service instance is up and running. + + + + For local development we recommend using the [Docker](https://hub.docker.com/_/postgres) container. + + You can start the container with the following command: + + ```bash + pnpm services:setup + ``` + + This will start all the services (including the database container) and initialize the database with the latest schema. + + **Where is DATABASE\_URL?** + + `DATABASE_URL` is a connection string that is used to connect to the database. When the command will finish it will be displayed in the console and setup to your environment variables. + + + + You can also use a cloud instance of database (e.g. [Supabase](/docs/web/recipes/supabase), [Neon](https://neon.tech/), [Turso](https://turso.tech/), etc.), although it's not recommended for local development. + + If you choose Supabase as your provider, follow the [Supabase recipe](/docs/web/recipes/supabase#configure-environment-variables) for details on configuring `DATABASE_URL` and running migrations. + + **Where is DATABASE\_URL?** + + It's available in your provider's project dashboard. You'll need to copy the connection string from there and add it to your `.env.local` file. The format will look something like: + + * Neon: `postgresql://user:password@ep-xyz-123.region.aws.neon.tech/dbname` + * Turso: `libsql://your-db-xyz.turso.io` + + Make sure to keep this URL secure and never commit it to version control. + + + +Then, you need to set `DATABASE_URL` environment variable in **root** `.env.local` file. + +```dotenv title=".env.local" +# The database URL is used to connect to your database. +DATABASE_URL="postgresql://postgres:postgres@127.0.0.1:54322/postgres" +``` + +You're ready to go! 🥳 + +## Studio + +TurboStarter provides you also with an interactive UI where you can explore your database and test queries called Studio. + +To run the Studio, you can use the following command: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:studio +``` + +This will start the Studio on [https://local.drizzle.studio](https://local.drizzle.studio). + +![Drizzle Studio](/images/docs/db-studio.webp) + +## Next steps + +* [Update schema](/docs/web/database/schema) - learn about schema and how to update it. +* [Generate & run migrations](/docs/web/database/migrations) - migrate your changes to the database. +* [Initialize client](/docs/web/database/client) - initialize the database client and start interacting with the database. diff --git a/.context/turbostarter-framework-context/sections/web/database/schema.md b/.context/turbostarter-framework-context/sections/web/database/schema.md new file mode 100644 index 0000000..c3c6b30 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/database/schema.md @@ -0,0 +1,75 @@ +--- +title: Schema +description: Learn about the database schema. +url: /docs/web/database/schema +--- + +# Schema + +Creating a schema for your data is one of the primary tasks when building a new application. + +You can find the schema of each table in `packages/db/src/db/schema` directory. The schema is basically organized by entity and each file is a separate table. + +## Defining schema + +The schema is defined using SQL-like utilities from [drizzle-orm](https://orm.drizzle.team/docs/sql-schema-declaration). + +It supports all the SQL features, such as enums, indexes, foreign keys, extensions and more. + + + We're relying on the [code-first approach](https://orm.drizzle.team/docs/migrations), where we define the schema in code and then generate the SQL from it. That way we can approach full type-safety and the simplest flow for database updates and migrations. + + +## Example + +Let's take a look at the `customer` table, where we store information about our customers. + +```typescript title="customer.ts" +export const customer = pgTable("customer", { + id: text().primaryKey().$defaultFn(generateId), + userId: text() + .references(() => user.id, { + onDelete: "cascade", + }) + .notNull() + .unique(), + customerId: text().notNull().unique(), + status: billingStatusEnum(), + plan: pricingPlanTypeEnum(), + createdAt: timestamp().notNull().defaultNow(), + updatedAt: timestamp() + .notNull() + .$onUpdate(() => new Date()), +}); +``` + +We're using a few native SQL utilities here, such as: + +* `pgTable` - a table definition. +* `primaryKey` - a primary key. +* `defaultFn` - a default function. +* `$onUpdate` - an on update function. +* `notNull` - a not null constraint. +* `defaultNow` - a default now function. +* `timestamp` - a timestamp. +* `text` - a text. +* `unique` - a unique constraint. +* `references` - a reference to another table. + +What's more, Drizzle gives us the ability to export the TypeScript types for the table, which we can reuse e.g. for the API calls. + +Also, we can use the drizzle extension [drizzle-zod](https://orm.drizzle.team/docs/zod) to generate the Zod schemas for the table. + +```typescript title="customer.ts" +import { createInsertSchema, createSelectSchema } from "drizzle-zod"; + +export const insertCustomerSchema = createInsertSchema(customer); +export const selectCustomerSchema = createSelectSchema(customer); +export const updateCustomerSchema = createUpdateSchema(customer); + +export type InsertCustomer = z.infer; +export type SelectCustomer = z.infer; +export type UpdateCustomer = z.infer; +``` + +Then we can use the generated schemas in our API handlers and frontend forms to validate the data. diff --git a/.context/turbostarter-framework-context/sections/web/deployment/amplify.md b/.context/turbostarter-framework-context/sections/web/deployment/amplify.md new file mode 100644 index 0000000..99cefb9 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/deployment/amplify.md @@ -0,0 +1,125 @@ +--- +title: AWS Amplify +description: Learn how to deploy your TurboStarter app to AWS Amplify. +url: /docs/web/deployment/amplify +--- + +# AWS Amplify + +[AWS Amplify](https://aws.amazon.com/amplify/) is a fully managed service that makes it easy to build, deploy, and host modern web applications. It provides features like continuous deployment, serverless functions, authentication, and more - all integrated into a seamless developer experience. + +This guide explains how to deploy your TurboStarter app on AWS Amplify. You'll learn how to set up your repository for automated deployments, configure build settings, manage environment variables, and ensure your application runs smoothly in production. **AWS Amplify handles the infrastructure management, allowing you to focus on developing your application.** + + + To deploy to AWS Amplify, you need to have an AWS account. You can create one [here](https://aws.amazon.com/amplify/). + + + + + ## Create configuration file + + To deploy your TurboStarter app to AWS Amplify, you need to create a config file. This file will contain the necessary information to connect your repository to AWS Amplify and deploy your application. + + Let's create a new file called `amplify.yml` in the root of your project: + + ```yaml title="amplify.yml" + version: 1 + applications: + - frontend: + buildPath: "/" + phases: + preBuild: + commands: + - npm install -g pnpm + - pnpm install + build: + commands: + - pnpm dlx turbo build --filter=web + artifacts: + baseDirectory: apps/web/.next + files: + - "**/*" + cache: + paths: + - node_modules/**/* + - apps/web/.next/cache/**/* + appRoot: apps/web + ``` + + This configuration file tells AWS Amplify how to build and deploy your application: + + * The `version` field specifies the Amplify configuration version + * Under `applications`, we define the build settings for our web app: + * `buildPath` indicates where to run the build commands + * `preBuild` phase installs pnpm and project dependencies + * `build` phase runs the Turborepo build command for the web app + * `artifacts` specifies which files to deploy (the Next.js build output) + * `cache` configures which directories to cache between builds + * `appRoot` points to the web application directory + + AWS Amplify will use this configuration to automatically build and deploy your app whenever you push changes to your repository. It also useful to define other resources that you can use and link to your project. + + + + ## Create a new Amplify project + + We'll use the [AWS Amplify](https://aws.amazon.com/amplify/) web interface to deploy our app. First, let's create a new project. + + ![Amplify create project](/images/docs/web/deployment/amplify/create-project.png) + + Proceed with the option to *Deploy an app*. + + + + ## Connect repository + + Choose the Git provider of your project and select the repository you want to deploy. + + ![Amplify connect repository](/images/docs/web/deployment/amplify/connect-repository.png) + + + If your repository is private you need to authorize Amplify to access it. It's recommended to follow a *least privileged access* approach, so to only grant access to the repository you want to deploy, not the entire account. + + + Select the branch you want to deploy and make sure to enable the *My app is a monorepo* option - configure it with the path to the app that you want to deploy (e.g. `apps/web`). + + ![Amplify repository and branch](/images/docs/web/deployment/amplify/repository.png) + + + + ## Configure build settings + + Finalize your deployment by configuring the build settings to match your project's specific needs. Refer to the points below to ensure a seamless deployment process. + + ![Amplify build settings](/images/docs/web/deployment/amplify/build-settings.png) + + Make sure that the build command and build output directory is set to the correct values (it should be defined based on your configuration file from Step 1.). + + ### Environment variables + + In the *Advanced settings* section, you can define environment variables that will be available to your application at runtime. + + ![Amplify environment variables](/images/docs/web/deployment/amplify/environment-variables.png) + + Verify that all required environment variables are defined, so your app can be build and deployed successfully. + + + + ## Review and deploy! + + On the next step, you'll be able to review the configuration that you've created and deploy your app. It's the right time to make sure that everything is set up correctly. + + ![Amplify review and deploy](/images/docs/web/deployment/amplify/review.png) + + After making sure that everything is set up correctly, you can click on the *Save and deploy* button to start the deployment process. + + When your app is deployed, you'll be able to access it via the URL provided in the Amplify console: + + ![Amplify deployed app](/images/docs/web/deployment/amplify/deployed.png) + + That's it! Your app is now deployed to AWS Amplify, congratulations! 🎉 + + + +Feel free to scale your deployment to multiple regions, add custom domains, and use other Amplify features to make your app more robust and scalable. +Check out the [AWS Amplify documentation](https://docs.aws.amazon.com/amplify/latest/userguide/welcome.html) for more information on how to use Amplify to its full potential. diff --git a/.context/turbostarter-framework-context/sections/web/deployment/api.md b/.context/turbostarter-framework-context/sections/web/deployment/api.md new file mode 100644 index 0000000..17eac8c --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/deployment/api.md @@ -0,0 +1,198 @@ +--- +title: Standalone API +description: Learn how to deploy your API as a dedicated service. +url: /docs/web/deployment/api +--- + +# Standalone API + +Sometimes you want to deploy your API as a standalone service. This is useful if you want to deploy your API to a different domain or to deploy it as a microservice. You can also follow this approach if you don't need a web app, but still need API service for [mobile app](/docs/mobile) or [browser extension](/docs/extension). + +Deploying your API as a standalone service provides enhanced flexibility and scalability. This allows you to independently scale your API from your web app. It's particularly beneficial for executing "long-running" tasks on your backend, such as report generation, real-time data processing, or background tasks that are likely to timeout in a serverless environment. + +This guide explains how to deploy your TurboStarter API as a standalone service. As Hono has multiple deployment options (e.g. [Deno](https://hono.dev/docs/getting-started/deno), [Bun](https://hono.dev/docs/getting-started/bun)), this guide will focus primarily on the [Node.js](https://hono.dev/docs/getting-started/nodejs) deployment. + + + + ## Create separate API app + + We have a [dedicated guide](/docs/web/customization/add-app) on how to add another app to your project. However, in this case, only a few files need to be added, so we can do it quickly here. + + First, let's create an `api` directory inside the `apps` directory - it will be the root of your API app. + + Next, add the following files into the `apps/api` directory: + + + + ```json + { + "name": "api", + "version": "0.1.0", + "private": true, + "scripts": { + "build": "esbuild ./src/index.ts --bundle --platform=node --outfile=dist/index.js", + "clean": "git clean -xdf dist .turbo node_modules", + "dev": "dotenv -c -- tsx watch src/index.ts", + "start": "node dist/index.js", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@hono/node-server": "1.13.7", + "@turbostarter/api": "workspace:*" + }, + "devDependencies": { + "@turbostarter/tsconfig": "workspace:*", + "@types/node": "20.16.10", + "esbuild": "0.24.2", + "tsx": "4.19.2", + "typescript": "catalog:" + } + } + ``` + + + + ```json + { + "extends": "@turbostarter/tsconfig/base.json", + "include": ["src"], + "exclude": ["node_modules"] + } + ``` + + + + ```ts + import { serve } from "@hono/node-server"; + import { appRouter } from "@turbostarter/api"; + + serve( + { + fetch: appRouter.fetch, + port: Number(process.env.PORT) || 3001, + }, + ({ port }) => { + console.log(`Server is running on ${port} 🚀`); + }, + ); + ``` + + + + This will enable you to have a minimal configuration required to run your API as a standalone service. For sure, you can add more configuration (e.g. ESLint or Prettier) if needed, we just want to keep it minimal for the sake of this guide. + + + + ## Connect web app to API + + The API will be running on a different URL than your web app. For the minimal setup and to avoid handling [cross-origin resource sharing (CORS)](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS) issues, we will rewrite the API URL in the web app. + + To do this, you will need to change your `next.config.ts` file to include the API URL rewrite: + + ```js title="apps/web/next.config.ts" + import type { NextConfig } from "next"; + + const config: NextConfig = { + rewrites: async () => [ + { + source: "/api/:path*", + destination: `${env.NEXT_PUBLIC_API_URL ?? "http://localhost:3001"}/api/:path*`, + }, + ], + }; + ``` + + + It's recommended to use an environment variable (e.g. `NEXT_PUBLIC_API_URL`) to set the API URL. This is a good practice to make it easier to change the API URL in different environments (e.g. development, staging, production). + + + Now you should be able to run your API as a standalone service. When you run the project with `pnpm dev`, you will see the new app called `api` with your API server running on [http://localhost:3001](http://localhost:3001). + + + + ## Deploy! + + You can basically deploy your API as any other Node.js project. We will quickly go through the two most popular options: [PaaS](https://en.wikipedia.org/wiki/Platform_as_a_service) and [Docker](https://www.docker.com/). + + ### Platform as a Service (PaaS) + + PaaS providers like [Vercel](https://vercel.com/), [Heroku](https://www.heroku.com/), or [Netlify](https://www.netlify.com/) allow you to deploy your Node.js app with a few clicks. You can follow our [dedicated guides](/docs/web/deployment/checklist#deploy-web-app-to-production) for the most popular providers. Every process is similar, and will contains a few crucial steps: + + 1. Connecting your repository to the PaaS provider + 2. Setting up build settings (e.g. build command, output directory) + 3. Setting up environment variables + 4. Deploying the project + + + To make sure your API is built and run correctly, you will need to ensure that appropriate commands are correctly set up. In our case, the following commands will need to be configured: + + + + ```bash + pnpm turbo build --filter=api + ``` + + + + ```bash + pnpm --filter=api start + ``` + + + + This is required to ensure that the PaaS provider of your choice will be able to build and run your application correctly. + + + ### Docker + + Deploying your API as a Docker container is a good option if you want to have more control over the deployment process. You can follow our [dedicated guide](/docs/web/deployment/docker) to learn how to deploy your API as a Docker container. + + For the API application, the `Dockerfile` will be located in the `apps/api` directory and it could look like this: + + ```dockerfile title="apps/api/Dockerfile" + FROM node:20-alpine AS base + ENV PNPM_HOME="/pnpm" + ENV PATH="$PNPM_HOME:$PATH" + RUN corepack enable + + FROM base AS pruner + WORKDIR /app + RUN apk add --no-cache libc6-compat + COPY . . + RUN pnpm dlx turbo prune api --docker + + FROM base AS builder + WORKDIR /app + RUN apk add --no-cache libc6-compat + COPY --from=pruner /app/out/json/ . + COPY --from=pruner /app/out/pnpm-lock.yaml ./pnpm-lock.yaml + RUN pnpm install --frozen-lockfile --ignore-scripts --prefer-offline && pnpm store prune + ENV SKIP_ENV_VALIDATION=1 \ + NODE_ENV=production + COPY --from=pruner /app/out/full/ . + RUN pnpm dlx turbo build --filter=api + + FROM base AS runner + WORKDIR /app + RUN addgroup -g 1001 -S nodejs && \ + adduser -S api -u 1001 -G nodejs + COPY --from=builder --chown=api:nodejs /app/apps/api/dist/ ./ + USER api + EXPOSE 3001 + CMD ["node", "index.js"] + ``` + + To test if everything works correctly, you can run a [container](https://docs.docker.com/get-started/03_run_your_app/) locally with the following commands: + + ```bash + docker build -f ./apps/api/Dockerfile . -t turbostarter-api + docker run -p 3001:3001 turbostarter-api + ``` + + Make sure to also [pass](https://docs.docker.com/reference/cli/docker/container/run/#env) all the required environment variables to the container, so your API can start without any issues. + + Deploying your API as a Docker container is a great way to isolate your API from the host environment, making it easier to deploy and scale. It also simplifies the workflow if you're working with a team, as you can easily share the Docker image with your colleagues and they will run the API in the **exact same** environment. + + + +That's it! You can now grow your API layer as a standalone service, separated from other apps in your project, and deploy it anywhere you want. diff --git a/.context/turbostarter-framework-context/sections/web/deployment/checklist.md b/.context/turbostarter-framework-context/sections/web/deployment/checklist.md new file mode 100644 index 0000000..6e70659 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/deployment/checklist.md @@ -0,0 +1,193 @@ +--- +title: Checklist +description: Let's deploy your TurboStarter app to production! +url: /docs/web/deployment/checklist +--- + +# Checklist + +When you're ready to deploy your project to production, follow this checklist. + +This process may take a few hours and some trial and error, so buckle up - you're almost there! + + + + ## Create database instance + + **Why it's necessary?** + + A production-ready database instance is essential for storing your application's data securely and reliably in the cloud. [PostgreSQL](https://www.postgresql.org/) is the recommended database for TurboStarter due to its robustness, features, and wide support. + + **How to do it?** + + You have several options for hosting your PostgreSQL database: + + * [Supabase](/docs/web/recipes/supabase) - Provides a fully managed Postgres database with additional features + * [Vercel Postgres](https://vercel.com/storage/postgres) - Serverless SQL database optimized for Vercel deployments + * [Neon](https://neon.tech/) - Serverless Postgres with automatic scaling + * [Turso](https://turso.tech/) - Edge database built on libSQL with global replication + * [DigitalOcean](https://www.digitalocean.com/products/managed-databases) - Managed database clusters with automated failover + + Choose a provider based on your needs for: + + * Pricing and budget + * Geographic region availability + * Scaling requirements + * Additional features (backups, monitoring, etc.) + + + + ## Migrate database + + **Why it's necessary?** + + Pushing database migrations ensures that your database schema in the remote database instance is configured to match TurboStarter's requirements. This step is crucial for the application to function correctly. + + **How to do it?** + + You basically have two possibilities of doing a migration: + + + + TurboStarter comes with predefined Github Actions workflow to handle database migrations. You can find its definition in the `.github/workflows/publish-db.yml` file. + + What you need to do is to set your `DATABASE_URL` as a [secret for your Github repository](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions). + + Then, you can run the workflow which will publish the database schema to your remote database instance. + + [Check how to run Github Actions workflow.](https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/manually-running-a-workflow) + + + + You can also run your migrations locally, although this is not recommended for production. + + To do so, set the `DATABASE_URL` environment variable to your database URL (that comes from your database provider) in `.env.local` file and run the following command: + + ```bash + pnpm with-env pnpm --filter @turbostarter/db db:migrate + ``` + + This command will run the migrations and apply them to your remote database. + + [Learn more about database migrations.](/docs/web/database/migrations) + + + + + + ## Configure OAuth Providers + + **Why it's necessary?** + + Configuring OAuth providers like [Google](https://www.better-auth.com/docs/authentication/google) or [Github](https://www.better-auth.com/docs/authentication/github) ensures that users can log in using their existing accounts, enhancing user convenience and security. This step involves setting up the OAuth credentials in the provider's developer console, configuring the necessary environment variables, and setting up callback URLs to point to your production app. + + **How to do it?** + + 1. Follow the provider-specific guides to set up OAuth credentials for the providers you want to use. For example: + * [Apple OAuth setup guide](https://www.better-auth.com/docs/authentication/apple) + * [Google OAuth setup guide](https://www.better-auth.com/docs/authentication/google) + * [Github OAuth setup guide](https://www.better-auth.com/docs/authentication/github) + 2. Once you have the credentials, set the corresponding environment variables in your project. For the example providers above: + * For Apple: `APPLE_CLIENT_ID` and `APPLE_CLIENT_SECRET` + * For Google: `GOOGLE_CLIENT_ID` and `GOOGLE_CLIENT_SECRET` + * For Github: `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET` + 3. Ensure that the callback URLs for each provider are set to point to your production app. **This is crucial for the OAuth flow to work correctly.** + + You can add or remove OAuth providers based on your needs. Just make sure to follow the provider's setup guide, set the required environment variables, and configure the callback URLs correctly. + + + + ## Setup billing provider + + **Why it's necessary?** + + Well - you want to get paid, right? Setting up billing ensures that you can charge your users for using your SaaS application, enabling you to monetize your service and cover operational costs. + + **How to do it?** + + * Create a [Stripe](/docs/web/billing/stripe), [Lemon Squeezy](/docs/web/billing/lemon-squeezy) or [Polar](/docs/web/billing/polar) account. + * Update the environment variables with the correct values for your billing service. + * Point webhooks from Stripe, Lemon Squeezy or Polar to `/api/billing/webhook`. + * Refer to the [relevant documentation](/docs/web/billing/overview) for more details on setting up billing. + + + + ## Setup emails provider + + **Why it's necessary?** + + Setting up an email provider is crucial for your SaaS application to send notifications, confirmations, and other important messages to your users. This enhances user experience and engagement, and is a standard practice in modern web applications. + + **How to do it?** + + * Create an account with an email service provider of your choice. See [available providers](/docs/web/emails/configuration#providers) for more information. + * Update the environment variables with the correct values for your email service. + * Refer to the [relevant documentation](/docs/web/emails/overview) for more details on setting up email. + + + + ## Setup storage provider + + **Why it's necessary?** + + Don't forget to configure your storage provider, if you want to operate on files in your app. By default, this is optional — the app can run without a storage provider — but some features could be unavailable (e.g., avatar uploads and other file-related actions). + + **How to do it?** + + * Review the [Storage overview](/docs/web/storage/overview). + * Follow [Storage configuration](/docs/web/storage/configuration) to choose and set up a provider. + * Add any required environment variables in your **hosting provider**. + + + + ## Environment variables + + **Why it's necessary?** + + Setting the correct environment variables is essential for the application to function correctly. These variables include API keys, database URLs, and other configuration details required for your app to connect to various services. + + **How to do it?** + + Use our `.env.example` files to get the correct environment variables for your project. Then add them to your **hosting provider's environment variables**. Redeploy the app once you have the URL to set in the environment variables. + + + + ## Deploy web app to production + + **Why it's necessary?** + + Because your users are waiting! Deploying your Next.js app to a hosting provider makes it accessible to users worldwide, allowing them to interact with your application. + + **How to do it?** + + Deploy your Next.js app to chosen hosting provider. **Copy the deployment URL and set it as an environment variable in your project's settings.** Feel free to check out our dedicated guides for the most popular hosting providers: + + + + + + + + + + + + + + + + + + We also have a dedicated guide for [deploying your API as a standalone service](/docs/web/deployment/api). + + + +That's it! Your app is now live and accessible to your users, good job! 🎉 + + + * Update the legal pages with your company's information (privacy policy, terms of service, etc.). + * Remove the placeholder blog and documentation content / or replace it with your own. + * Customize authentication emails and other email templates. + * Update the favicon and logo with your own branding. + * Update the FAQ and other static content with your own information. + diff --git a/.context/turbostarter-framework-context/sections/web/deployment/docker.md b/.context/turbostarter-framework-context/sections/web/deployment/docker.md new file mode 100644 index 0000000..170885b --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/deployment/docker.md @@ -0,0 +1,93 @@ +--- +title: Docker +description: Learn how to containerize your TurboStarter app with Docker. +url: /docs/web/deployment/docker +--- + +# Docker + +[Docker](https://docker.com) is a popular platform for containerizing applications, making it easy to package your app with all its dependencies for consistent performance across environments. It simplifies development, testing, and deployment. + +This guide explains how to containerize your TurboStarter app using Docker. You'll learn to create a Dockerfile, build a container image, and run your app in a container for a reliable and portable setup. + + + + ## Configure Next.js for Docker + + First of all, we need to configure Next.js to output the build files in the [standalone format](https://nextjs.org/docs/pages/api-reference/config/next-config-js/output) - it's required for the Docker image to work. To do this, we need to add the following to our `next.config.ts` file: + + ```js title="apps/web/next.config.ts" + import type { NextConfig } from "next"; + + const config: NextConfig = { + output: "standalone", + + ... + }; + ``` + + + + ## Create a Dockerfile + + [Dockerfile](https://docs.docker.com/get-started/02_our_app/) is a text file that contains the instructions for building a [Docker image](https://docs.docker.com/get-started/02_our_app/). It defines the environment, dependencies, and commands needed to run your app. You can safely copy the following Dockerfile to your project: + + ```dockerfile title="apps/web/Dockerfile" + FROM node:22-alpine AS base + ENV PNPM_HOME="/pnpm" + ENV PATH="$PNPM_HOME:$PATH" + RUN corepack enable + + FROM base AS pruner + WORKDIR /app + RUN apk add --no-cache libc6-compat + COPY . . + RUN pnpm dlx turbo prune web --docker + + FROM base AS builder + WORKDIR /app + RUN apk add --no-cache libc6-compat + COPY --from=pruner /app/out/json/ . + COPY --from=pruner /app/out/pnpm-lock.yaml ./pnpm-lock.yaml + RUN pnpm install --frozen-lockfile --ignore-scripts --prefer-offline && pnpm store prune + ENV SKIP_ENV_VALIDATION=1 \ + NODE_ENV=production + COPY --from=pruner /app/out/full/ . + RUN pnpm dlx turbo build --filter=web + + FROM base AS runner + WORKDIR /app + RUN addgroup -g 1001 -S nodejs && \ + adduser -S web -u 1001 -G nodejs + COPY --from=builder --chown=web:nodejs /app/apps/web/.next/standalone ./ + COPY --from=builder --chown=web:nodejs /app/apps/web/.next/static ./apps/web/.next/static + COPY --from=builder --chown=web:nodejs /app/apps/web/public ./apps/web/public + USER web + EXPOSE 3000 + CMD ["node", "apps/web/server.js"] + ``` + + Feel free to check out our [self-hosting guide](/blog/self-host-your-nextjs-turborepo-app-with-docker-in-5-minutes) for more details on how each stage of the Dockerfile works. + + And that's all we need! You can now build and run your Docker image to deploy your app anywhere you want in an [isolated environment](https://docs.docker.com/get-started/workshop/04_sharing_app/). + + + + ## Run a container + + To test if everything works correctly, you can run a [container](https://www.docker.com/resources/what-container/) locally with the following commands: + + ```bash + docker build -f ./apps/web/Dockerfile . -t turbostarter + docker run -p 3000:3000 turbostarter + ``` + + Make sure to also [pass](https://docs.docker.com/reference/cli/docker/container/run/#env) all the required environment variables to the container, so your app can start without any issues. + + If everything works correctly, you should be able to access your app at [http://localhost:3000](http://localhost:3000). + + + +That's it! You can now build and deploy your app as a Docker container to any supported hosting (e.g. [Fly.io](/docs/web/deployment/fly)). + +Using Docker containers is a great way to isolate your app from the host environment, making it easier to deploy and scale. It also simplifies the workflow if you're working with a team, as you can easily share the Docker image with your colleagues and they will run the app in the **exact same** environment. diff --git a/.context/turbostarter-framework-context/sections/web/deployment/fly.md b/.context/turbostarter-framework-context/sections/web/deployment/fly.md new file mode 100644 index 0000000..f37845d --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/deployment/fly.md @@ -0,0 +1,112 @@ +--- +title: Fly.io +description: Learn how to deploy your TurboStarter app to Fly.io. +url: /docs/web/deployment/fly +--- + +# Fly.io + +[Fly.io](https://fly.io) makes deploying web applications to the cloud easy and efficient. It handles scaling, monitoring, and logging so you can focus on building your app. + +This guide explains how to deploy your TurboStarter app on Fly.io. You'll learn how to leverage [Docker](/docs/web/deployment/docker) containers to deploy your app, set up builds, and manage environment variables for a smooth and reliable deployment. + + + To deploy to Fly.io, you need to have an account. You can create one [here](https://fly.io/app/sign-up). + + You also need to have [Docker](/docs/web/deployment/docker) configured in your project. + + + + + ## Setup Fly CLI + + As we will be using Fly CLI to launch and manage our app, you need to install and setup it on your machine. + + [Check the official documentation on how to install Fly CLI](https://fly.io/docs/flyctl/install/). + + After you've installed Fly CLI, you need to login to your Fly account and connect it with your machine: + + ```bash + fly auth login + ``` + + [Read more about authenticating CLI](https://fly.io/docs/flyctl/auth/#available-commands). + + Now you're ready to launch your app! + + + + ## Launch project + + Use a [Dockerfile](/docs/web/deployment/docker) to launch your app with [Fly CLI](https://fly.io/docs/flyctl/). You can use the following command to do this from your local machine: + + ```bash + fly launch --dockerfile apps/web/Dockerfile + ``` + + Make sure to set all the required configuration in the CLI steps (e.g. set port to `3000`, setup additional services, choose billing plan, etc.). + + ![Fly launch](/images/docs/web/deployment/fly/launch.png) + + + If you want to achieve better performance and lower latency in your API requests, you can customize the region of your Render service. Make sure to set it to the region closest to your database and users. + + + After the launch is complete, Fly will output your project configuration into `fly.toml` file. The configuration of your project is stored there, feel free to customize it to your needs: + + ```toml title="fly.toml" + app = 'web-aged-sky-5596' + primary_region = 'ams' + + [build] + dockerfile = 'apps/web/Dockerfile' + + [http_service] + internal_port = 3000 + force_https = true + auto_stop_machines = 'stop' + auto_start_machines = true + min_machines_running = 0 + processes = ['app'] + + [[vm]] + memory = '512mb' + cpu_kind = 'shared' + cpus = 1 + ``` + + See [Fly.io documentation](https://fly.io/docs/reference/configuration) for more information on how to use this file. + + + + ## Set up secrets + + To make your app fully functional, you need to set up required environment variables. You can do this by running the following command: + + ```bash + fly secrets set --app DATABASE_URL=... + ``` + + They will be automatically added to your app's runtime environment. + + + + ## Deploy! + + Each time you make changes to `fly.toml` or secrets, you need to re-deploy your app to apply changes to the running app. + + To do this, just run the following command in your project directory: + + ```bash + fly deploy + ``` + + This will build your app and deploy it to Fly.io with the latest code version. + + ![Fly deploy](/images/docs/web/deployment/fly/deploy.png) + + That's it! Your app is now deployed to Fly.io, congratulations! 🎉 + + + +Fly is a platform that allows you to deploy and manage applications in the cloud. It provides a simple and intuitive way to deploy your app, with features such as automatic scaling, load balancing, and rolling updates. With Fly, you can focus on building your app without worrying about the underlying infrastructure. diff --git a/.context/turbostarter-framework-context/sections/web/deployment/netlify.md b/.context/turbostarter-framework-context/sections/web/deployment/netlify.md new file mode 100644 index 0000000..4d6800e --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/deployment/netlify.md @@ -0,0 +1,67 @@ +--- +title: Netlify +description: Learn how to deploy your TurboStarter app to Netlify. +url: /docs/web/deployment/netlify +--- + +# Netlify + +[Netlify](https://netlify.com) is a powerful platform for deploying modern web applications. It offers continuous deployment, serverless functions, and a global CDN to ensure your application is fast and reliable. + +In this guide, we will walk through the steps to deploy your TurboStarter app to Netlify. You will learn how to connect your repository, configure build settings, and manage environment variables to ensure a smooth deployment process. + + + To deploy to Netlify, you need to have an account. You can create one [here](https://netlify.com/signup). + + + + + ## Create new site + + Once you've created your account and logged in, the Netlify dashboard will display an option to add a new site. Click on the *Import from Git* button to begin connecting your Git repository. + + ![Create new site](/images/docs/web/deployment/netlify/create-site.png) + + If you've already had a Netlify account, you can get to this step by clicking on the *Sites* tab in the navigation menu. + + + + ## Connect your repository + + Choose the Git provider of your project and select the repository you want to deploy. + + ![Connect repository](/images/docs/web/deployment/netlify/connect-repository.png) + + + To connect your repository, you need to authorize Netlify to access it. It's recommended to follow a *least privileged access* approach, so to only grant access to the repository you want to deploy, not the entire account. + + + + + ## Configure build settings + + Last step before deploying! Configure the build settings according to your project configuration. Use the screenshots provided below for reference to ensure a smooth deployment process. + + ![Netlify build settings](/images/docs/web/deployment/netlify/build-settings.png) + + Also, add all environment variables under *Environment variables* section - it's required to make the build process work. + + + + ## Deploy! + + Click on the *Deploy* button to start the deployment process. + + ![Netlify deploy](/images/docs/web/deployment/netlify/deploy.png) + + That's it! Your app is now deployed to Netlify, congratulations! 🎉 + + + + + If you want to achieve better performance and lower latency in your API requests, you can customize the region of your Netlify serverless functions. Make sure to set it to the region closest to your database and users. + + ![Netlify region](/images/docs/web/deployment/netlify/region.png) + + Unfortunately, it's a paid feature, so you need to upgrade your Netlify account to be able to change it. + diff --git a/.context/turbostarter-framework-context/sections/web/deployment/railway.md b/.context/turbostarter-framework-context/sections/web/deployment/railway.md new file mode 100644 index 0000000..b3824bd --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/deployment/railway.md @@ -0,0 +1,82 @@ +--- +title: Railway +description: Learn how to deploy your TurboStarter app to Railway. +url: /docs/web/deployment/railway +--- + +# Railway + +[Railway](https://railway.app) is a platform that allows you to deploy your web applications to a cloud environment. It provides a simple and efficient way to manage your application's infrastructure, including scaling, monitoring, and logging. + +This guide provides a step-by-step walkthrough for deploying your TurboStarter app on Railway, and taking advantage of its features in production environment. You'll discover how to link your repository, tailor build settings, and oversee environment variables, ensuring a smooth and optimized deployment process that leverages Railway's capabilities. + + + To deploy to Railway, you need to have an account. You can create one [here](https://railway.app/signup). + + + + + ## Create new project + + We'll use [Railway](https://railway.app) web app to deploy our project. First, let's create a new project. + + ![Railway create project](/images/docs/web/deployment/railway/create-project.png) + + Proceed with the option to *Deploy from Github repo*. + + + + ## Connect repository + + Choose the Git provider of your project and select the repository you want to deploy. + + ![Connect repository](/images/docs/web/deployment/railway/connect-repository.png) + + + If your repository is private you need to authorize Railway to access it. It's recommended to follow a *least privileged access* approach, so to only grant access to the repository you want to deploy, not the entire account. + + + + + ## Configure project settings + + Finalize your deployment by configuring the build settings to match your project's specific needs. Refer to the points below to ensure a seamless deployment process. + + ### Commands + + Configure the build and start commands to ensure that your project is built and started correctly. + + ![Railway project commands](/images/docs/web/deployment/railway/commands.png) + + Make sure to set them to the following values: + + * **Build command** - `pnpm dlx turbo build --filter=web` + * **Start command** - `pnpm --filter=web start` + + ### Environment variables + + Last, but not least, you need to set the environment variables for your project. Make sure to check if all the required variables are set. + + ![Railway environment variables](/images/docs/web/deployment/railway/environment-variables.png) + + + If you want to achieve better performance, lower latency in your API requests or add some replicas of your application, you can customize the region of your Railway instance. Make sure to set it to the region closest to your database and users. + + ![Railway region](/images/docs/web/deployment/railway/region.png) + + + You can also use a [Railway config file](https://docs.railway.com/guides/config-as-code) to manage your project's settings in one place, as a code. + + + + ## Deploy! + + Click on the *Deploy* button to start the deployment process. + + ![Railway deploy](/images/docs/web/deployment/railway/deploy.png) + + That's it! Your app is now deployed to Railway, congratulations! 🎉 + + + +Feel free to scale your deployment to multiple regions or isolate it in the separate network. Check out the [Railway documentation](https://docs.railway.app) for more information about which services are available. diff --git a/.context/turbostarter-framework-context/sections/web/deployment/render.md b/.context/turbostarter-framework-context/sections/web/deployment/render.md new file mode 100644 index 0000000..58e82a9 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/deployment/render.md @@ -0,0 +1,94 @@ +--- +title: Render +description: Learn how to deploy your TurboStarter app to Render. +url: /docs/web/deployment/render +--- + +# Render + +[Render](https://render.com) offers a unique combination of features that make it an ideal platform for deploying modern web applications. With Render, you can leverage continuous deployment, managed databases, and a global CDN to ensure your application is not only fast and reliable but also scalable and secure. + +In this guide, we will walk through the steps to deploy your TurboStarter app to Render, highlighting the benefits of using Render's platform. You will learn how to connect your repository, configure build settings, and manage environment variables to ensure a seamless and efficient deployment process that takes advantage of Render's features. + + + To deploy to Render, you need to have an account. You can create one [here](https://dashboard.render.com/register). + + + + + ## Create a new service + + Navigate to the [Render dashboard](https://dashboard.render.com) and click on the *New* button. + + ![Create new service](/images/docs/web/deployment/render/create-service.png) + + Pick the *Web Service* option and proceed to the next step. + + + + ## Connect your repository + + Choose the Git provider of your project and select the repository you want to deploy. + + ![Connect repository](/images/docs/web/deployment/render/connect-repository.png) + + + If your repository is private you need to authorize Render to access it. It's recommended to follow a *least privileged access* approach, so to only grant access to the repository you want to deploy, not the entire account. + + + + + ## Configure service settings + + Finalize your deployment by configuring the build settings to match your project's specific needs. Refer to the screenshots below to ensure a seamless deployment process. + + ![Render service settings](/images/docs/web/deployment/render/general-settings.png) + + You can also group your service with other services (e.g. [databases](https://render.com/docs/postgresql-creating-connecting) or [cron jobs](https://render.com/docs/cronjobs)) in a [Project](https://render.com/docs/projects), which will help you manage them together. + + [Read official documentation for more information](https://render.com/docs/projects). + + + If you want to achieve better performance and lower latency in your API requests, you can customize the region of your Render service. Make sure to set it to the region closest to your database and users. + + + ### Commands + + Configure the build and start commands to ensure that your project is built and started correctly. + + ![Render service commands](/images/docs/web/deployment/render/commands.png) + + Make sure to set them to the following values: + + * **Build command** - `pnpm install --frozen-lockfile; pnpm dlx turbo build --filter=web` + * **Start command** - `pnpm --filter=web start` + + ### Instance type + + Select a plan that fits your project's needs. + + ![Render instance type](/images/docs/web/deployment/render/instance-type.png) + + For testing purposes or MVPs, you can safely use the *Free* plan. Although, for the production version, it's recommended to upgrade your plan, as it offers more resources and your project won't be paused after periods of inactivity. + + ### Environment variables + + Last, but not least, you need to set the environment variables for your project. Make sure to check if all the required variables are set. + + ![Render environment variables](/images/docs/web/deployment/render/environment-variables.png) + + You can also modify *Advanced settings* to set e.g. [health checks](https://render.com/docs/deploys#health-checks) or modify [auto deploy](https://render.com/docs/deploys#automatic-git-deploys) triggers. + + + + ## Deploy! + + Click on the *Deploy Web Service* button to start the deployment process. + + ![Render deploy](/images/docs/web/deployment/render/deploy.png) + + That's it! Your app is now deployed to Render, congratulations! 🎉 + + + +Render is a powerful platform with a lot of integrations and features. Feel free to check out the [official documentation](https://render.com/docs) for more information. diff --git a/.context/turbostarter-framework-context/sections/web/deployment/vercel.md b/.context/turbostarter-framework-context/sections/web/deployment/vercel.md new file mode 100644 index 0000000..c94b098 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/deployment/vercel.md @@ -0,0 +1,215 @@ +--- +title: Vercel +description: Learn how to deploy your TurboStarter app to Vercel. +url: /docs/web/deployment/vercel +--- + +# Vercel + +In general you can deploy the application to any hosting provider that supports Node.js, but we recommend using [Vercel](https://vercel.com) for the best experience. + +Vercel is the easiest way to deploy Next.js apps. It's the company behind Next.js and has first-class support for Next.js. + + + To deploy to Vercel, you need to have an account. You can create one [here](https://vercel.com/signup). + + +TurboStarter has two, separate ways to deploy to Vercel, each ships with **one-click deployment**. Choose the one that best fits your needs. + + + + Deploying with this method is the easiest and fastest way to get your app up and running on the cloud provider. Follow these steps: + + + + ## Connect your git repository + + After signing up you will be promted to import a git repository. Select the git provider of your project and connect your git account with Vercel. + + ![Vercel import project](/images/docs/web/deployment/vercel/connect-repository.webp) + + + + ## Configure project settings + + As we're working in monorepo, some additional settings are required to make the build process work. + + Make sure to set the following settings: + + * **Build command**: `pnpm turbo build --filter=web` - to build only the web app + * **Root directory**: `apps/web` - to make sure Vercel uses the web folder as the root directory (make sure to check *Include files outside the root directory in the Build Step* option, it will ensure that all packages from your monorepo are included in the build process) + + ![Vercel project settings](/images/docs/web/deployment/vercel/project-settings.png) + + + + + + + + + + ## Configure environment variables + + Please make sure to set all the environment variables required for the project to work correctly. You can find the list of required environment variables in the `.env.example` file in the `apps/web` directory. + + The environment variables can be set in the Vercel dashboard under *Project Settings* > *Environment Variables*. Make sure to set them for all environments (Production, Preview, and Development) as needed. + + **Failure to set the environment variables will result in the project not working correctly.** + + If the build fails, deep dive into the logs to see what is the issue. Our Zod configuration will validate and report any missing environment variables. To find out which environment variables are missing, please check the logs. + + + The first time this may fail if you don't yet have a custom domain connected since you cannot place it in the environment variables yet. It's fine. Make the first deployment fail, then pick the domain and add it. Redeploy. + + + + + ## Deploy! + + Click on the *Deploy* button to start the deployment process. + + ![Vercel deploy](/images/docs/web/deployment/vercel/success.png) + + That's it! Your app is now deployed to Vercel, congratulations! 🎉 + + + + + + Despite connecting your repository is the easiest way to deploy to Vercel, we recommend using preconfigured Github Actions for the most granular control over your deployments. + + We'll leverage [Vercel CLI](https://vercel.com/docs/cli) to deploy the application on the CI/CD pipeline. [See official documentation on deploying to Github Actions](https://vercel.com/guides/how-can-i-use-github-actions-with-vercel). + + + + ## Get Vercel Access Token + + To deploy the application, we need to get Vercel access token. + + Please, follow [this guide](https://vercel.com/guides/how-do-i-use-a-vercel-api-access-token) to create one. + + ![Vercel access token](/images/docs/web/deployment/vercel/access-token.avif) + + + + ## Install Vercel CLI + + We need to install [Vercel CLI](https://vercel.com/docs/cli) locally to be able to get required credentials for our Github Actions. + + You can install it using following command: + + ```bash + pnpm i -g vercel + ``` + + Then, login to Vercel using following command: + + ```bash + vercel login + ``` + + + + ## Get credentials + + Inside your folder, run following command to create a new project: + + ```bash + vercel link + ``` + + This will generate a `.vercel` folder, where you can find `project.json` file with `projectId` and `orgId`. + + + + ## Configure Github Actions + + Inside GitHub, add `VERCEL_TOKEN`, `VERCEL_ORG_ID`, and `VERCEL_PROJECT_ID` as [secrets](https://docs.github.com/en/actions/security-for-github-actions/security-guides/using-secrets-in-github-actions) to your repository. + + ![Github secrets](/images/docs/web/deployment/vercel/github-tokens.png) + + This will allow Github Actions to access your settings and deploy the application to Vercel. + + + + ## Configure project settings + + As we're working in monorepo, some additional settings are required to make the build process work. + + Make sure to set the following settings: + + * **Build command**: `pnpm turbo build --filter=web` - to build only the web app + * **Root directory**: `apps/web` - to make sure Vercel uses the web folder as the root directory (make sure to check *Include files outside the root directory in the Build Step* option, it will ensure that all packages from your monorepo are included in the build process) + + ![Vercel project settings](/images/docs/web/deployment/vercel/project-settings.png) + + + + + + + + + + ## Configure environment variables + + Please make sure to set all the environment variables required for the project to work correctly. You can find the list of required environment variables in the `.env.example` file in the `apps/web` directory. + + The environment variables can be set in the Vercel dashboard under *Project Settings* > *Environment Variables*. Make sure to set them for all environments (Production, Preview, and Development) as needed. + + **Failure to set the environment variables will result in the project not working correctly.** + + If the build fails, deep dive into the logs to see what is the issue. Our Zod configuration will validate and report any missing environment variables. To find out which environment variables are missing, please check the logs. + + + The first time this may fail if you don't yet have a custom domain connected since you cannot place it in the environment variables yet. It's fine. Make the first deployment fail, then pick the domain and add it. Redeploy. + + + + + ## Deploy! + + By default, TurboStarter comes with a Github Actions workflow that can be [triggered manually](https://docs.github.com/en/actions/managing-workflow-runs-and-deployments/managing-workflow-runs/manually-running-a-workflow). + + The configuration is located in `.github/workflows/publish-web.yml`, you can easily customize it to your needs, for example to trigger a deployment from `main` branch. + + ```diff title=".github/workflows/publish-web.yml" + on: + - workflow_dispatch: + + push: + + branches: + + - main + ``` + + Then, every time you push to `main` branch, the workflow will be triggered and the application will be deployed to Vercel. + + ![Vercel deploy](/images/docs/web/deployment/vercel/success.png) + + That's it! Your app is now deployed to Vercel, congratulations! 🎉 + + + + + + + +## Troubleshooting + +In some cases, users have reported issues with the deployment to Vercel using the default parameters. If you encounter problems, try these troubleshooting steps: + +1. **Check root directory settings** + * Set the root directory to `apps/web` + * Enable *Include source files outside of the Root Directory* option + +2. **Verify build configuration** + * Ensure the framework preset is set to Next.js + * Set build command to `pnpm turbo build --filter=web` + * Set install command to `pnpm install` + +3. **Review deployment logs** + * If deployment fails, carefully review the build logs + * Look for any error messages about missing dependencies or environment variables + * Verify that all required environment variables are properly configured + +If issues persist after trying these steps, check the [deployment troubleshooting guide](/docs/web/troubleshooting/deployment) for additional help. diff --git a/.context/turbostarter-framework-context/sections/web/emails/configuration.md b/.context/turbostarter-framework-context/sections/web/emails/configuration.md new file mode 100644 index 0000000..cc86e9f --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/emails/configuration.md @@ -0,0 +1,391 @@ +--- +title: Configuration +description: Learn how to configure your emails in TurboStarter. +url: /docs/web/emails/configuration +--- + +# Configuration + +The `@turbostarter/email` package provides a simple and flexible way to send emails using various email providers. It abstracts the complexity of different email services and offers a consistent interface for sending emails with pre-defined templates. + +To configure the email service, you need to set an `EMAIL_FROM` environment variable. It will be used as the sender of the emails. **Please make sure that the mail address and domain are verified in your mail provider.** + +```dotenv +EMAIL_FROM="hello@resend.dev" +``` + +The email provider is configured by modifying the exports in `packages/email` package. By default, [Nodemailer](/docs/web/emails/configuration#nodemailer) is used. + +Configuration will be validated against the schema, so you will see the error messages in the console if something is not right. + +## Providers + +TurboStarter supports multiple email providers, each with its own configuration. Below, you'll find detailed information on how to set up and use each supported provider. Choose the one that best fits your needs and follow the instructions in the respective accordion section. + + + + To use Resend as your email provider, you need to [create an account](https://resend.com/) and [obtain your API key](https://resend.com/docs/dashboard/api-keys/introduction). + + Then, set it as an environment variable in your `.env.local` file in `apps/web` directory and your deployment environment: + + ```dotenv + RESEND_API_KEY="your-api-key" + ``` + + Also, make sure to activate Resend as your email provider by updating the exports in: + + + + ```ts + // [!code word:resend] + export * from "./resend"; + ``` + + + + ```ts + // [!code word:resend] + export * from "./resend/env"; + ``` + + + + To customize the provider, you can find its definition in `packages/email/src/providers/resend` directory. + + For more information, please refer to the [Resend documentation](https://resend.com/docs). + + + + To use SendGrid as your email provider, you need to [create an account](https://sendgrid.com/) and [obtain your API key](https://sendgrid.com/docs/ui/account-and-settings/api-keys/). + + Then, set it as an environment variable in your `.env.local` file in `apps/web` directory and your deployment environment: + + ```dotenv + SENDGRID_API_KEY="your-api-key" + ``` + + Also, make sure to activate SendGrid as your email provider by updating the exports in: + + + + ```ts + // [!code word:sendgrid] + export * from "./sendgrid"; + ``` + + + + ```ts + // [!code word:sendgrid] + export * from "./sendgrid/env"; + ``` + + + + To customize the provider, you can find its definition in `packages/email/src/providers/sendgrid` directory. + + For more information, please refer to the [SendGrid documentation](https://sendgrid.com/docs). + + + + To use Postmark as your email provider, you need to [create an account](https://postmarkapp.com/) and [obtain your server API token](https://postmarkapp.com/support/article/1008-what-are-the-account-and-server-api-tokens). + + Then, set it as an environment variable in your `.env.local` file in `apps/web` directory and your deployment environment: + + ```dotenv + POSTMARK_API_KEY="your-secret-api-token" + ``` + + Also, make sure to activate Postmark as your email provider by updating the exports in: + + + + ```ts + export * from "./postmark"; + ``` + + + + ```ts + // [!code word:postmark] + export * from "./postmark/env"; + ``` + + + + To customize the provider, you can find its definition in `packages/email/src/providers/postmark` directory. + + For more information, please refer to the [Postmark documentation](https://postmarkapp.com/developer). + + + + To use Plunk as your email provider, you need to [create an account](https://plunk.dev/) and [obtain your API key](https://docs.useplunk.com/api-reference/authentication). + + Then, set it as an environment variable in your `.env.local` file in `apps/web` directory and your deployment environment: + + ```dotenv + PLUNK_API_KEY="your-api-key" + ``` + + Also, make sure to activate Plunk as your email provider by updating the exports in: + + + + ```ts + // [!code word:plunk] + export * from "./plunk"; + ``` + + + + ```ts + // [!code word:plunk] + export * from "./plunk/env"; + ``` + + + + To customize the provider, you can find its definition in `packages/email/src/providers/plunk` directory. + + For more information, please refer to the [Plunk documentation](https://docs.useplunk.com). + + + + If you're using the `nodemailer` as your email provider, you'll need to set the following SMTP configuration in your environment variables: + + ```dotenv + NODEMAILER_HOST="your-smtp-host" + NODEMAILER_PORT="your-smtp-port" + NODEMAILER_USER="your-smtp-user" + NODEMAILER_PASSWORD="your-smtp-password" + ``` + + The variables are: + + * `NODEMAILER_HOST`: The host of your SMTP server. + * `NODEMAILER_PORT`: The port of your SMTP server. + * `NODEMAILER_USER`: The email address user of your SMTP server. + * `NODEMAILER_PASSWORD`: The password for the email account. + + Also, make sure to activate nodemailer as your email provider by updating the exports in: + + + + ```ts + // [!code word:nodemailer] + export * from "./nodemailer"; + ``` + + + + ```ts + // [!code word:nodemailer] + export * from "./nodemailer/env"; + ``` + + + + To customize the provider, you can find its definition in `packages/email/src/providers/nodemailer` directory. + + For more information, please refer to the [nodemailer documentation](https://nodemailer.com/smtp/). + + + +## Templates + +In the `@turbostarter/email` package, we provide a set of pre-defined templates for you to use. You can find them in the `packages/email/src/templates` directory. + +When you run your development server, you will be able to preview all available templates in the browser under [http://localhost:3005](http://localhost:3005). + +![Email preview](/images/docs/web/emails/development.png) + +Next to the templates, you can also find some shared components that you can use in your emails. The file structure looks like this: + + + + + + + + + + + +Feel free to add your own templates and components or modify existing ones to match them with your brand and style. + +### How to add a new template? + +We'll go through the process of adding a new template, as it requires a few steps to make sure everything works correctly. + + + + #### Define types + + Let's assume that we want to add a **welcome email**, that new users will receive after signing up. + + We'll start with defining new template type in `packages/email/src/types/templates.ts` file: + + ```ts title="templates.ts" + export const EmailTemplate = { + ...AuthEmailTemplate, + WELCOME: "welcome", + } as const; + ``` + + Also, we would need to add types for variables that we'll pass to the template (if any), in our case it will be just a `name` of the user: + + ```ts title="templates.ts" + type WelcomeEmailVariables = { + welcome: { + name: string; + }; + }; + + export type EmailVariables = AuthEmailVariables | WelcomeEmailVariables; + ``` + + By doing this, we ensure that payload passed to the template will have all required properties and we won't end up with an email that tells your user "Hey, undefined!". + + + + #### Create template + + Next up, we need to create a file with the template itself. We'll create an `welcome.tsx` file in `packages/email/src/templates` directory. + + ```tsx title="welcome.tsx" + import { Heading, Preview, Text } from "@react-email/components"; + + import { Button } from "../_components/button"; + import { Layout } from "../_components/layout/layout"; + + import type { EmailTemplate, EmailVariables } from "../../types"; + + type Props = EmailVariables[typeof EmailTemplate.WELCOME]; + + export const Welcome = ({ name }: Props) => { + return ( + + Welcome to TurboStarter! + Hi, {name}! + + Start your journey with our app by clicking the button below. + + + + ); + }; + + Welcome.subject = "Welcome to TurboStarter!"; + + Welcome.PreviewProps = { + name: "John Doe", + }; + + export default Welcome; + ``` + + As you can see, by defining appropriate types for the template, we can safely use the variables as a props in the template. + + To learn more about supported components, please refer to the [React Email documentation](https://react.email/docs/components). + + + + #### Register template + + We have to register the template in the main entrypoint of the templates in `packages/email/src/templates/index.ts` file: + + ```ts title="index.ts" + import { Welcome } from "./welcome"; + + export const templates = { + ... + [EmailTemplate.WELCOME]: Welcome, + } as const; + ``` + + That way, it will be available in the `sendEmail` function, enabling us to send it from the server-side of your application. + + ```ts + import { sendEmail } from "@turbostarter/email/server"; + + sendEmail({ + to: "user@example.com", + template: EmailTemplate.WELCOME, + variables: { + name: "John Doe", + }, + }); + ``` + + Learn more about sending emails in the [dedicated section](/docs/web/emails/sending). + + + +Et voilà! You've just added a new email template to your application 🎉 + +### Translating templates + +You can also translate your templates to support multiple languages. Each mail template is passed the `locale` property, which you can use to get the translation for the current locale. This allows you to maintain consistent translations across your application and emails. + +The translation system [uses the same i18n setup](/docs/web/internationalization/overview) as your main application, so you can reuse your existing translation files and namespaces. The translations are loaded server-side when the email is generated, ensuring the correct language is used based on the user's preferences. + +Here's how you can implement translations in your email templates: + +```tsx +import { Heading, Preview, Text } from "@react-email/components"; + +import { getTranslation } from "@turbostarter/i18n/server"; + +import { Button } from "../_components/button"; +import { Layout } from "../_components/layout/layout"; + +import type { + EmailTemplate, + EmailVariables, + CommonEmailProps, +} from "../../types"; + +type Props = EmailVariables[typeof EmailTemplate.WELCOME] & CommonEmailProps; + +export const Welcome = async ({ name, locale }: Props) => { + const { t } = await getTranslation({ locale, ns: "auth" }); + + return ( + + {t("account.welcome.preview")} + {t("account.welcome.heading", { name })} + + {t("account.welcome.body")} + + + + ); +}; + +Welcome.subject = async ({ locale }: CommonEmailProps) => { + const { t } = await getTranslation({ locale, ns: "auth" }); + return t("account.welcome.subject"); +}; + +Welcome.PreviewProps = { + name: "John Doe", + locale: "en", +}; + +export default Welcome; +``` + +To send the email in the specified language, you can pass the optional `locale` argument to the `sendEmail` function: + +```ts +sendEmail({ + to: "user@example.com", + template: EmailTemplate.WELCOME, + variables: { + name: "John Doe", + }, + locale: "en", // [!code highlight] +}); +``` + +Learn more about translations in the [dedicated section](/docs/web/internationalization/translations). diff --git a/.context/turbostarter-framework-context/sections/web/emails/overview.md b/.context/turbostarter-framework-context/sections/web/emails/overview.md new file mode 100644 index 0000000..a79afbc --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/emails/overview.md @@ -0,0 +1,45 @@ +--- +title: Overview +description: Get started with emails in TurboStarter. +url: /docs/web/emails/overview +--- + +# Overview + +For mailing functionality, TurboStarter integrates [React Email](https://react.email/docs/introduction) which enables you to build your emails from composable React components. + + + It's a simple, yet powerful library that allows you to **write your emails in React**. + + It also allows you to use **Tailwind CSS for styling**, which is a huge advantage, as we can share almost everything from the main app with the emails package, keeping them consistent with rest of the app. + + +You can read more about `react-email` package in the [official documentation](https://react.email/docs/introduction). + +## Providers + +TurboStarter implements multiple providers for managing and sending emails. To learn more about each provider and how to configure them, see the respective section: + + + + + + + + + + + + + +All configuration and setup is built-in with a unified API, so you can switch between providers by simply changing the exports and even introduce your own provider without breaking any sending-related logic. + +## Development + +When you [setup your development environment](/docs/web/installation/development) and run `pnpm dev` command a new app will start at [http://localhost:3005](http://localhost:3005). + +![Email preview](/images/docs/web/emails/development.png) + +There you'll be able to check your email templates and send test emails from your app. It includes hot-reloading, so when you make change in the code - it will be reflected in the browser. + +Learn more about configuration and setup of the emails in TurboStarter in the following sections. diff --git a/.context/turbostarter-framework-context/sections/web/emails/sending.md b/.context/turbostarter-framework-context/sections/web/emails/sending.md new file mode 100644 index 0000000..d5f4ee8 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/emails/sending.md @@ -0,0 +1,114 @@ +--- +title: Sending emails +description: Learn how to send emails in TurboStarter. +url: /docs/web/emails/sending +--- + +# Sending emails + +The strategy for sending emails, that every provider has to implement, is **extremely simple**: + +```ts +export interface EmailProviderStrategy { + send: (args: { + to: string; + subject: string; + text: string; + html?: string; + }) => Promise; +} +``` + + + You don't need to worry much about it, as all the providers are already configured for you. Just be aware of it if you want to add your custom provider. + + +Then, we define a general `sendEmail` function that you can use as an API for sending emails in your app: + +```ts +const sendEmail = async ({ + to, + template, + variables, + locale, +}: { + to: string; + template: T; + variables: EmailVariables[T]; + locale?: string; +}) => { + const { html, text, subject } = await getTemplate({ + id: template, + variables, + locale, + }); + + return send({ to, subject, html, text }); +}; +``` + +The arguments are: + +* `to`: The recipient's email address. +* `template`: The email template to use. +* `variables`: The variables to pass to the template. +* `locale`: The locale to use for the email. + +It returns a promise that resolves when the email is sent successfully. If there is an error, the promise will be rejected with an error message. + +To send an email, just invoke the `sendEmail` with the correct arguments from the **server-side** of your application: + +```ts +import { sendEmail } from "@turbostarter/email/server"; + +sendEmail({ + to: "user@example.com", + template: EmailTemplate.WELCOME, + variables: { + name: "John Doe", + }, + locale: "en", +}); +``` + +And that's it! You're ready to send emails in your application 🚀 + +## Authentication emails + +TurboStarter comes with a set of pre-configured authentication emails for various purposes, including magic links and password reset functionality. + +To handle the sending of these emails at the right time, we use [Better Auth Hooks](https://www.better-auth.com/docs/concepts/email), which trigger when specific authentication events occur. + +The logic for determining which email to send is already implemented for you in the `packages/auth/src/server.ts` file, alongside your [authentication configuration](/docs/web/auth/configuration): + +```ts title="server.ts" +export const auth = betterAuth({ + emailAndPassword: { + enabled: true, + sendResetPassword: async ({ user, url }) => + sendEmail({ + to: user.email, + template: EmailTemplate.RESET_PASSWORD, + variables: { + url, + }, + }), + }, + emailVerification: { + sendVerificationEmail: async ({ user, url }) => + sendEmail({ + to: user.email, + template: EmailTemplate.CONFIRM_EMAIL, + variables: { + url, + }, + }), + }, + + /* other options */ +}); +``` + +As you can see, the authentication emails are automatically sent when needed (e.g. when user requests password reset or needs to verify their email address). + +You can customize authentication templates by modifying them in the `packages/email/src/templates` directory, or create your own templates for other use cases in your application. diff --git a/.context/turbostarter-framework-context/sections/web/extras.md b/.context/turbostarter-framework-context/sections/web/extras.md new file mode 100644 index 0000000..67b6e99 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/extras.md @@ -0,0 +1,66 @@ +--- +title: Extras +description: See what you get together with the code. +url: /docs/web/extras +--- + +# Extras + +## Tips and Tricks + +In many places, next to the code you will find some marketing tips, design suggestions, and potential risks. This is to help you build a better product and avoid common pitfalls. + +```tsx title="Hero.tsx" +return ( +
+ {/* 💡 Use something that user can visualize e.g. + "Ship your startup while on the toilet" */} +

Best startup on the world

+
+); +``` + +### Submission tips + +When it comes to mobile app and browser extension, you must submit your product to review from Apple/Google etc. We have some tips for you to make sure your submission goes smoothly. + +```json title="app.json" +{ + "ios": { + "infoPlist": { + /* 🍎 add descriptive justification of using this permission on iOS */ + "NSCameraUsageDescription": "This app uses the camera to scan barcodes on event tickets." + } + } +} +``` + +As well as providing you with the info on how to make your store listings better: + +```json title="package.json" +{ + "manifest": { + /* 💡 Use localized messages to get more visibility in web stores */ + "name": "__MSG_extensionName__", + "default_locale": "en" + } +} +``` + +## Discord community + +We have a Discord community where you can ask questions and share your projects. It's a great place to get help and meet other developers. Check more details at [/discord](/discord). + + + +![Discord](/images/docs/discord.png) + +## 25+ SaaS Ideas + +Not sure what to build? We have a list of **25+** SaaS ideas that you can use to get started 🔥 + +Grouped by category, these ideas are a great way to get inspired and start building your next project. + +Including design, copies, marketing tips and potential risks, this list is a great resource for anyone looking to build a SaaS product. + +![SaaS Ideas](/images/docs/saas-ideas.png) diff --git a/.context/turbostarter-framework-context/sections/web/faq.md b/.context/turbostarter-framework-context/sections/web/faq.md new file mode 100644 index 0000000..555e3d7 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/faq.md @@ -0,0 +1,92 @@ +--- +title: FAQ +description: Find answers to common technical questions. +url: /docs/web/faq +--- + +# FAQ + +## Why isn't everything hidden and configured with one BIG config file? + +TurboStarter intentionally exposes the underlying code rather than hiding it behind configuration files (like some starters do). This design choice follows our **you own your code** philosophy, giving you full control and flexibility over your codebase. + +While a single config file might seem simpler initially, it often becomes restrictive when you need to customize functionality beyond what the config allows. With direct access to the code, you can modify any part of the system to match your specific requirements. + +## I don't know some technology! Should I buy TurboStarter? + +You should be prepared for a learning curve or consider learning it first. However, TurboStarter will still work for you if you're willing to learn. + +Even without knowing some technologies, you can still use the rest of the features. + +## I don't need mobile app or browser extension, what should I do? + +You can simply ignore the mobile app and browser extension parts of the project. You can remove the `apps/mobile` and `apps/extension` directories from the project. + +The modular nature of TurboStarter allows you to remove parts of the project that you don't need without affecting the rest of the stack. + +## I want to use a different provider for X + +Sure! TurboStarter is designed to be modular, so configuring new provider (e.g. for emails, billing or any other service) is straightforward. You just need to make sure your configuration is compatible with common interface to be able to plug it into the codebase. + +## Will you add more packages in the future? + +Yes, we will keep updating TurboStarter with new packages and features. This kit is designed to be modular, allowing for new features and packages to be added without interfering with your existing code. You can always [update your project](/docs/web/installation/update) to the latest version. + +## Can I use this kit for a non-SaaS project? + +This kit is mainly designed for SaaS projects. If you're building something other than a SaaS, the Next.js SaaS Boilerplate might include features you don't need. You can still use it for non-SaaS projects, but you may need to remove or modify features that are specific to SaaS use cases. + +## Can I use personal accounts only? + +Yes! You can disable team accounts and have personal accounts only by setting a feature flag. + +## Does it set up the production instance for me? + +No, TurboStarter does not set up the production instance for you. This includes setting up databases, Stripe, or any other services you need. TurboStarter does not have access to your Stripe or Resend accounts, so setup on your end is required. TurboStarter provides the codebase and documentation to help you set up your SaaS project. + +## Does the starter include Solito? + +No. Solito will not be included in this repo. It is a great tool if you want to share code between your Next.js and Expo app. However, the main purpose of this repo is not the integration between Next.js and Expo — it's the code splitting of your SaaS platforms into a monorepo. You can utilize the monorepo with multiple apps, and it can be any app such as Vite, Electron, etc. + +Integrating Solito into this repo isn't hard, and there are a few [official templates](https://github.com/nandorojo/solito/tree/master/example-monorepos) by the creators of Solito that you can use as a reference. + +## Does this pattern leak backend code to my client applications? + +No, it does not. The `api` package should only be a production dependency in the Next.js application where it's served. The Expo app, browser extension, and all other apps you may add in the future should only add the `api` package as a dev dependency. This lets you have full type safety in your client applications while keeping your backend code safe. + +If you need to share runtime code between the client and server, you can create a separate `shared` package for this and import it on both sides. + +## How do I get support if I encounter issues? + +For support, you can: + +1. Visit our [Discord](https://discord.gg/KjpK2uk3JP) +2. Contact us via support email ([hello@turbostarter.dev](mailto:hello@turbostarter.dev)) + +## Are there any example projects or demos? + +Yes - feel free to check out our demo app at [demo.turbostarter.dev](https://demo.turbostarter.dev). Also, you can get inspired by projects built by our customers - take a look at [Showcase](/#showcase). + +## How do I deploy my application? + +Please check the [production checklist](/docs/web/deployment/checklist) for more information. + +## How do I update my project when a new version of the boilerplate is released? + +Please read the [documentation for updating your TurboStarter code](/docs/web/installation/update). + +## Can I use the React package X with this kit? + +Yes, you can use any React package with this kit. The kit is based on React, so you are generally only constrained by the underlying technologies and not by the kit itself. Since you own and can edit all the code, you can adapt the kit to your needs. However, if there are limitations with the underlying technology, you might need to work around them. + +## Can I integrate TurboStarter into an existing project? + +TurboStarter is a full-stack starter intended to be used as the foundation of your app. You can copy individual modules or patterns into an existing codebase, but retrofitting the entire starter into a mature project is typically not recommended and is not officially supported. If you choose to copy parts, prefer isolating boundaries (e.g., `packages/` modules) and aligning interfaces first. + +## Where can I deploy my application? + +TurboStarter targets modern Node.js/Next.js runtimes. You can deploy to providers that support these environments, such as [Vercel](/docs/web/deployment/vercel), [Railway](/docs/web/deployment/railway), [Render](/docs/web/deployment/render), [Fly](/docs/web/deployment/fly), or [Netlify](/docs/web/deployment/netlify) - following their Next.js guidance. Review our [production checklist](/docs/web/deployment/checklist) before going live. + +## Can I easily swap providers (billing, email, etc.)? + +Yes. The starter organizes integrations behind clear interfaces so you can replace providers (e.g., billing or email) with minimal surface changes. Keep your implementation behind a module boundary and adapt to the existing types to avoid ripple effects. diff --git a/.context/turbostarter-framework-context/sections/web/index.md b/.context/turbostarter-framework-context/sections/web/index.md new file mode 100644 index 0000000..d568b9f --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/index.md @@ -0,0 +1,336 @@ +--- +title: Introduction +description: Get started with TurboStarter web kit. +url: /docs/web +--- + +# Introduction + +Welcome to the TurboStarter documentation. This is your starting point for learning about the starter kit, its structure, features, and how to use it for your app development. + + + +## What is TurboStarter? + +TurboStarter is a fullstack starter kit that helps you build scalable and production-ready web apps, mobile apps, and browser extensions in minutes. + +Looking to bootstrap your project quickly? Check out our [TurboStarter CLI guide](/blog/the-only-turbo-cli-you-need-to-start-your-next-project-in-seconds) to get started in seconds. + +## Demo apps + +TurboStarter provides a suite of live demo applications you can try instantly - right in your browser, on your phone, or via browser extensions. Try them live by clicking the buttons below. + + + +## Principles + +TurboStarter is built with the following principles: + +* **As simple as possible** - It should be easy to understand, easy to use, and strongly avoid overengineering things. +* **As few dependencies as possible** - It should have as few dependencies as possible to allow you to take full control over every part of the project. +* **As performant as possible** - It should be fast and light without any unnecessary overhead. + +## Features + +Before diving into the technical details, let's overview the features TurboStarter provides. + +### Multi-platform development + +* [Web](/docs/web/stack): Build web apps with React, Next.js, and Tailwind CSS. +* [Mobile](/docs/mobile/stack): Build mobile apps with React Native and Expo. +* [Browser extension](/docs/extension/stack): Build browser extensions with React and WXT. + +For those interested in AI development, check out our dedicated [TurboStarter AI documentation](/ai/docs) with specialized features for building AI-powered applications. + + + Most features are available on all platforms. You can use the **same codebase** to build web, mobile, and browser extension apps. + + +### Authentication + + + + + + + + + + + + + + + + + + + +### Organizations/teams + + + + + + + + + + + +### Billing + + + + + + + + + + + + + + + +### Database + + + + + + + + + + + +### API + + + + + + + + + + + + + +### Admin + + + + + + + + + + + +### AI + + + + Seamless integration of OpenAI, Anthropic, Groq, Mistral, and Gemini. For more advanced AI features, check out [TurboStarter AI](/ai/docs). + + + + + + + + + +### Internationalization + + + + + + + + + + + +### Emails + + + + + + + + + + + +### Landing page + + + + + + + + + + + + + + + +### Marketing + + + + + + + + + + + + + + + + + +### Storage + + + + + + + +### CMS + + + + + + + +### Theming + + + + + + + + + + + +### Analytics + + + + + + + + + + + +### Monitoring + + + + + + + + + + + +### Deployment + + + + + + + + + + + +### Testing + + + + + + + + + + + +## Use like LEGO blocks + +The biggest advantage of TurboStarter is its modularity. You can use the entire stack or just the parts you need. It's like LEGO blocks - you can build anything you want with it. + +If you don't need a specific feature, feel free to remove it without affecting the rest of the stack. + +This approach allows for: + +* **Easy feature integration** - plug new features into the kit with minimal changes. +* **Simplified maintenance** - keep the codebase clean and maintainable. +* **Core feature separation** - distinguish between core features and custom features. +* **Additional modules** - easily add modules like billing, CMS, monitoring, logger, mailer, and more. + +## Scope of this documentation + +While building a SaaS application involves many moving parts, this documentation focuses specifically on TurboStarter. For in-depth information on the underlying technologies, please refer to their respective official documentation. + +This documentation will guide you through configuring, running, and deploying the kit, and will provide helpful links to the official documentation of technologies where necessary. + +## `llms.txt` + +You can access the entire TurboStarter documentation in Markdown format at [/llms.txt](/llms.txt). This can be used to ask any LLM (assuming it has a big enough context window) questions about the TurboStarter based on the most up-to-date documentation. + +### Example usage + +For instance, to prompt an LLM with questions about the TurboStarter: + +1. Copy the documentation contents from [/llms.txt](/llms.txt) +2. Use the following prompt format: + +``` +Documentation: +{paste documentation here} +--- +Based on the above documentation, answer the following: +{your question} +``` + +## Enjoy! + +This documentation is designed to be easy to follow and understand. If you have any questions or need help, feel free to reach out to us at [hello@turbostarter.dev](mailto:hello@turbostarter.dev). + +Explore new features, build amazing apps, and have fun! 🚀 diff --git a/.context/turbostarter-framework-context/sections/web/installation/clone.md b/.context/turbostarter-framework-context/sections/web/installation/clone.md new file mode 100644 index 0000000..0de2380 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/installation/clone.md @@ -0,0 +1,65 @@ +--- +title: Cloning repository +description: Get the code to your local machine and start developing. +url: /docs/web/installation/clone +--- + +# Cloning repository + + + Ensure you have Git installed on your local machine before proceeding. You can download Git from [here](https://git-scm.com). + + +## Git clone + +Clone the repository using the following command: + +```bash +git clone git@github.com:turbostarter/core +``` + +By default, we're using [SSH](https://docs.github.com/en/authentication/connecting-to-github-with-ssh) for all Git commands. If you don't have it configured, please refer to the [official documentation](https://docs.github.com/en/authentication/connecting-to-github-with-ssh) to set it up. + +Alternatively, you can use HTTPS to clone the repository: + +```bash +git clone https://github.com/turbostarter/core +``` + +Another alternative could be to use the [Github CLI](https://cli.github.com/) or [Github Desktop](https://desktop.github.com/) for Git operations. + + + +## Git remote + +After cloning the repository, remove the original origin remote: + +```bash +git remote rm origin +``` + +Add the upstream remote pointing to the original repository to pull updates: + +```bash +git remote add upstream git@github.com:turbostarter/core +``` + +Once you have your own repository set up, add your repository as the origin: + +```bash +git remote add origin +``` + + + +## Staying up to date + +To pull updates from the upstream repository, run the following command daily (preferably with your morning coffee ☕): + +```bash +git pull upstream main +``` + +This ensures your repository stays up to date with the latest changes. + +Check [Updating codebase](/docs/web/installation/update) for more details on updating your codebase. diff --git a/.context/turbostarter-framework-context/sections/web/installation/commands.md b/.context/turbostarter-framework-context/sections/web/installation/commands.md new file mode 100644 index 0000000..714bdd2 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/installation/commands.md @@ -0,0 +1,353 @@ +--- +title: Common commands +description: Learn about common commands you need to know to work with the project. +url: /docs/web/installation/commands +--- + +# Common commands + + + For sure, you don't need these commands to kickstart your project, but it's useful to know they exist for when you need them. + + + + You can set up aliases for these commands in your shell configuration file. For example, you can set up an alias for `pnpm` to `p`: + + ```bash title="~/.bashrc" + alias p='pnpm' + ``` + + Or, if you're using [Zsh](https://ohmyz.sh/), you can add the alias to `~/.zshrc`: + + ```bash title="~/.zshrc" + alias p='pnpm' + ``` + + Then run `source ~/.bashrc` or `source ~/.zshrc` to apply the changes. + + You can now use `p` instead of `pnpm` in your terminal. For example, `p i` instead of `pnpm install`. + + + + To inject environment variables into the command you run, prefix it with `with-env`: + + ```bash + pnpm with-env + ``` + + For example, `pnpm with-env pnpm build` will run `pnpm build` with the environment variables injected. + + Some commands, like `pnpm dev`, automatically inject the environment variables for you. + + +## Installing dependencies + +To install the dependencies, run: + +```bash +pnpm install +``` + +## Starting development server + +Start development server by running: + +```bash +pnpm dev +``` + +## Building project + +To build the project (including all apps and packages), run: + +```bash +pnpm build +``` + +## Building specific app/package + +To build a specific app/package, run: + +```bash +pnpm turbo build --filter= +``` + +## Cleaning project + +To clean the project, run: + +```bash +pnpm clean +``` + +Then, reinstall the dependencies: + +```bash +pnpm install +``` + +## Formatting code + +To check for formatting errors using Prettier, run: + +```bash +pnpm format +``` + +To fix formatting errors using Prettier, run: + +```bash +pnpm format:fix +``` + +## Linting code + +To check for linting errors using ESLint, run: + +```bash +pnpm lint +``` + +To fix linting errors using ESLint, run: + +```bash +pnpm lint:fix +``` + +## Typechecking + +To typecheck the code using TypeScript for any type errors, run: + +```bash +pnpm typecheck +``` + +## Adding UI components + + + + To add a new web component, run: + + ```bash + pnpm --filter @turbostarter/ui-web ui:add + ``` + + This command will add and export a new component to `@turbostarter/ui-web` package. + + + + To add a new mobile component, run: + + ```bash + pnpm --filter @turbostarter/ui-mobile ui:add + ``` + + This command will add and export a new component to `@turbostarter/ui-mobile` package. + + + +## Services commands + + + To run the services containers locally, you need to have [Docker](https://www.docker.com/) installed on your machine. + + You can always use the cloud-hosted solution (e.g. [Neon](https://neon.tech/), [Turso](https://turso.tech/) for database) for your projects. + + +We have a few commands to help you manage the services containers (for local development). + +### Starting containers + +To start the services containers, run: + +```bash +pnpm services:start +``` + +It will run all the services containers. You can check their configs in `docker-compose.yml`. + +### Setting up services + +To setup all the services, run: + +```bash +pnpm services:setup +``` + +It will start all the services containers and run necessary setup steps. + +### Stopping containers + +To stop the services containers, run: + +```bash +pnpm services:stop +``` + +### Displaying status + +To check the status and logs of the services containers, run: + +```bash +pnpm services:status +``` + +### Displaying logs + +To display the logs of the services containers, run: + +```bash +pnpm services:logs +``` + +### Database commands + +We have a few commands to help you manage the database leveraging [Drizzle CLI](https://orm.drizzle.team/kit-docs/commands). + +#### Generating migrations + +To generate a new migration, run: + +```bash +pnpm with-env turbo db:generate +``` + +It will create a new migration `.sql` file in the `packages/db/migrations` folder. + +#### Running migrations + +To run the migrations against the db, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:migrate +``` + +It will apply all the pending migrations. + +#### Pushing changes directly + + + Make sure you know what you're doing before pushing changes directly to the db. + + +To push changes directly to the db, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:push +``` + +It lets you push your schema changes directly to the database and omit managing SQL migration files. + +#### Checking database status + +To check the status of the database, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:status +``` + +It will display the status of the applied migrations and the pending ones. + +```bash +Applied migrations: +- 0000_cooing_vargas +- 0001_curious_wallflower +- 0002_good_vertigo +- 0003_peaceful_devos +- 0004_fat_mad_thinker +- 0005_yummy_bucky +- 0006_glorious_vargas + +Pending migrations: +- 0007_nebulous_havok +``` + +#### Resetting database + +To reset the database, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:reset +``` + +It will reset the database to the initial state. + +#### Seeding database + +To seed the database with some example data (for development purposes), run: + +```bash +pnpm with-env turbo db:seed +``` + +It will populate your database with some example data. + +#### Checking database + +To check the database schema consistency, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:check +``` + +#### Studying database + +To study the database schema in the browser, run: + +```bash +pnpm with-env pnpm --filter @turbostarter/db db:studio +``` + +This will start the Studio on [https://local.drizzle.studio](https://local.drizzle.studio). + +## Tests commands + +### Running tests + +To run the tests, run: + +```bash +pnpm test +``` + +This will run all the tests in the project using Turbo tasks. As it leverages Turbo caching, it's [recommended](/docs/web/tests/unit#configuration) to run it in your CI/CD pipeline. + +### Running tests projects + +To run tests for all Vitest [Test Projects](https://vitest.dev/guide/projects), run: + +```bash +pnpm test:projects +``` + +This will run all the tests in the project using Vitest. + +### Watching tests + +To watch the tests, run: + +```bash +pnpm test:projects:watch +``` + +This will watch the tests for all [Test Projects](https://vitest.dev/guide/projects) and run them automatically when you make changes. + +### Generating code coverage + +To generate code coverage report, run: + +```bash +pnpm turbo test:coverage +``` + +This will generate a code coverage report in the `coverage` directory under `tooling/vitest` package. + +### Viewing code coverage + +To preview the code coverage report in the browser, run: + +```bash +pnpm turbo test:coverage:view +``` + +This will launch the report's `.html` file in your default browser. diff --git a/.context/turbostarter-framework-context/sections/web/installation/conventions.md b/.context/turbostarter-framework-context/sections/web/installation/conventions.md new file mode 100644 index 0000000..7559567 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/installation/conventions.md @@ -0,0 +1,86 @@ +--- +title: Conventions +description: Some standard conventions used across TurboStarter codebase. +url: /docs/web/installation/conventions +--- + +# Conventions + +You're not required to follow these conventions: they're simply a standard set of practices used in the core kit. If you like them - we encourage you to keep these during your usage of the kit - so to have consistent code style that you and your teammates understand. + +## Turborepo Packages + +In this project, we use [Turborepo packages](https://turbo.build/repo/docs/core-concepts/internal-packages) to define reusable code that can be shared across multiple applications. + +* **Apps** are used to define the main application, including routing, layout, and global styles. +* **Packages** shares reusable code add functionalities across multiple applications. They're configurable from the main application. + + + **Recommendation:** Do not create a package for your app code unless you plan to reuse it across multiple applications or are experienced in writing library code. + + If your application is not intended for reuse, keep all code in the app folder. This approach saves time and reduces complexity, both of which are beneficial for fast shipping. + + **Experienced developers:** If you have the experience, feel free to create packages as needed. + + +## Imports and Paths + +When importing modules from packages or apps, use the following conventions: + +* **From a package:** Use `@turbostarter/package-name` (e.g., `@turbostarter/ui`, `@turbostarter/api`, etc.). +* **From an app:** Use `~/` (e.g., `~/components`, `~/config`, etc.). + +## Enforcing conventions + +* [Prettier](https://prettier.io/) is used to enforce code formatting. +* [ESLint](https://eslint.org/) is used to enforce code quality and best practices. +* [TypeScript](https://www.typescriptlang.org/) is used to enforce type safety. + + + + + + + + + +## Code health + +TurboStarter provides a set of tools to ensure code health and quality in your project. + +### Github Actions + +By default, TurboStarter sets up Github Actions to run tests on every push to the repository. You can find the Github Actions configuration in the `.github/workflows` directory. + +The workflow has multiple stages: + +* `format` - runs Prettier to format the code. +* `lint` - runs ESLint to check for linting errors. +* `typecheck` - runs TypeScript to check for type errors. + +### Git hooks + +Together with TurboStarter we have set up a `commit-msg` hook which will check if your commit message follows the [conventional commit](https://www.conventionalcommits.org/en/v1.0.0/) message format. This is important for generating changelogs and keeping a clean commit history. + +Although we didn't ship any pre-commit hooks (we believe in shipping fast with moving checking code responsibility to CI), you can easily add them by using [Husky](https://typicode.github.io/husky/#/). + +#### Setting up the Pre-Commit Hook + +To do so, create a `pre-commit` file in the `./..husky` directory with the following content: + +```bash +#!/bin/sh + +pnpm typecheck +pnpm lint +``` + +Turborepo will execute the commands for all the affected packages - while skipping the ones that are not affected. + +#### Make the Pre-Commit Hook Executable + +```bash +chmod +x ./.husky/pre-commit +``` + +To test the pre-commit hook, try to commit a file with linting errors or type errors. The commit should fail, and you should see the error messages in the console. diff --git a/.context/turbostarter-framework-context/sections/web/installation/dependencies.md b/.context/turbostarter-framework-context/sections/web/installation/dependencies.md new file mode 100644 index 0000000..c957427 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/installation/dependencies.md @@ -0,0 +1,73 @@ +--- +title: Managing dependencies +description: Learn how to manage dependencies in your project. +url: /docs/web/installation/dependencies +--- + +# Managing dependencies + +As the package manager we chose [pnpm](https://pnpm.io/). + + + It is a fast, disk space efficient package manager that uses hard links and symlinks to save one version of a module only ever once on a disk. It also has a great [monorepo support](https://pnpm.io/workspaces). Of course, you can change it to use [Bun](https://bunpkg.com), [yarn](https://yarnpkg.com) or [npm](https://www.npmjs.com) with minimal effort. + + +## Install dependency + +To install a package you need to decide whether you want to install it to the root of the monorepo or to a specific workspace. Installing it to the root makes it available to all packages, while installing it to a specific workspace makes it available only to that workspace. + +To install a package globally, run: + +```bash +pnpm add -w +``` + +To install a package to a specific workspace, run: + +```bash +pnpm add --filter +``` + +For example: + +```bash +pnpm add --filter @turbostarter/ui motion +``` + +It will install `motion` to the `@turbostarter/ui` workspace. + +## Remove dependency + +Removing a package is the same as installing but with the `remove` command. + +To remove a package globally, run: + +```bash +pnpm remove -w +``` + +To remove a package from a specific workspace, run: + +```bash +pnpm remove --filter +``` + +## Update a package + +Updating is a bit easier since there is a nice way to update a package in all workspaces at once: + +```bash +pnpm update -r +``` + + + When you update a package, pnpm will respect the [semantic versioning](https://docs.npmjs.com/about-semantic-versioning) rules defined in the `package.json` file. If you want to update a package to the latest version, you can use the `--latest` flag. + + +## Renovate bot + +By default, TurboStarter comes with [Renovate](https://www.npmjs.com/package/renovate) enabled. It is a tool that helps you manage your dependencies by automatically creating pull requests to update your dependencies to the latest versions. You can find its configuration in the `.github/renovate.json` file. Learn more about it in the [official docs](https://docs.renovatebot.com/configuration-options/). + +When it creates a pull request, it is treated as a normal PR, so all tests and preview deployments will run. **It is recommended to always preview and test the changes in the staging environment before merging the PR to the main branch to avoid breaking the application.** + + diff --git a/.context/turbostarter-framework-context/sections/web/installation/development.md b/.context/turbostarter-framework-context/sections/web/installation/development.md new file mode 100644 index 0000000..aca9df9 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/installation/development.md @@ -0,0 +1,89 @@ +--- +title: Development +description: Get started with the code and develop your SaaS. +url: /docs/web/installation/development +--- + +# Development + +## Prerequisites + +To get started with TurboStarter, ensure you have the following installed and set up: + +* [Node.js](https://nodejs.org/en) (22.x or higher) +* [Docker](https://www.docker.com) (only if you want to use local services e.g. database) +* [pnpm](https://pnpm.io) + +## Project development + + + + ### Install dependencies + + Install the project dependencies by running the following command: + + ```bash + pnpm i + ``` + + + It is a fast, disk space efficient package manager that uses hard links and symlinks to save one version of a module only ever once on a disk. It also has a great [monorepo support](https://pnpm.io/workspaces). Of course, you can change it to use [Bun](https://bunpkg.com), [yarn](https://yarnpkg.com) or [npm](https://www.npmjs.com) with minimal effort. + + + + + ### Setup environment variables + + Create a `.env.local` files from `.env.example` files and fill in the required environment variables. + + You can use the following command to recursively copy the `.env.example` files to the `.env.local` files: + + + + ```bash + find . -name ".env.example" -exec sh -c 'cp "$1" "${1%.example}.local"' _ {} \; + ``` + + + + ```bash + Get-ChildItem -Recurse -Filter ".env.example" | ForEach-Object { + Copy-Item $_.FullName ($\_.FullName -replace '\.example$', '.local') + } + ``` + + + + Check [Environment variables](/docs/web/configuration/environment-variables) for more details on setting up environment variables. + + + + ### Setup services + + If you want to use local services like [database](/docs/web/database/overview) (**recommended for development purposes**), ensure Docker is running, then setup them with: + + ```bash + pnpm services:setup + ``` + + This command initiates the containers and runs necessary setup steps, ensuring your services are up to date and ready to use. + + + + ### Start development server + + To start the application development server, run: + + ```bash + pnpm dev + ``` + + Your app should now be up and running at [http://localhost:3000](http://localhost:3000) 🎉 + + + + ### Deploy to Production + + When you're ready to deploy the project to production, follow the [checklist](/docs/web/deployment/checklist) to ensure everything is set up correctly. + + diff --git a/.context/turbostarter-framework-context/sections/web/installation/editor-setup.md b/.context/turbostarter-framework-context/sections/web/installation/editor-setup.md new file mode 100644 index 0000000..8c81373 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/installation/editor-setup.md @@ -0,0 +1,69 @@ +--- +title: Editor setup +description: Learn how to set up your editor for the fastest development experience. +url: /docs/web/installation/editor-setup +--- + +# Editor setup + +Of course you can use every IDE you like, but you will have the best possible developer experience with this starter kit when using **VSCode-based** editor with the suggested settings and extensions. + +## Settings + +We have included most recommended settings in the `.vscode/settings.json` file to make your development experience as smooth as possible. It include mostly configs for tools like Prettier, ESLint and Tailwind which are used to enforce some conventions across the codebase. You can adjust them to your needs. + +## LLM rules + +We exposed a special endpoint that will scan all the docs and return the content as a text file which you can use to train your LLM or put in a prompt. You can find it at [/llms.txt](/llms.txt). + +The repository also includes a custom rules for most popular AI editors and agents to ensure that AI completions are working as expected and following our conventions. + +### AGENTS.md + +We've integrated specific rules that help maintain code quality and ensure AI-assisted completions align with our project standards. + +You can find them in the `AGENTS.md` file at the root of the project. This format is a standardized way to instruct AI agents to follow project conventions when generating code and it's used by over **20,000** open-source projects - including [Cursor](https://cursor.sh/), [Aider](https://aider.chat/), [Codex from OpenAI](https://openai.com/blog/openai-codex/), [Jules from Google](https://jules.ai/), [Windsurf](https://windsurf.dev/), and many others. + +```md title="AGENTS.md" +### Code Style and Structure + +- Write concise, technical TypeScript code with accurate examples +- Use functional and declarative programming patterns; avoid classes +- Prefer iteration and modularization over code duplication + +### Naming Conventions + +.... +``` + +To learn more about `AGENTS.md` rules check out the [official documentation](https://agents.md/). + +## Extensions + +Once you cloned the project and opened it in VSCode you should be promted to install suggested extensions which are defined in the `.vscode/extensions.json` automatically. In case you rather want to install them manually you can do so at any time later. + +These are the extensions we recommend: + +### ESLint + +Global extension for static code analysis. It will help you to find and fix problems in your JavaScript code. + + + +### Prettier + +Global extension for code formatting. It will help you to keep your code clean and consistent. + + + +### Pretty TypeScript Errors + +Improves TypeScript error messages shown in the editor. + + + +### Tailwind CSS IntelliSense + +Adds IntelliSense for Tailwind CSS classes to enable autocompletion and linting. + + diff --git a/.context/turbostarter-framework-context/sections/web/installation/structure.md b/.context/turbostarter-framework-context/sections/web/installation/structure.md new file mode 100644 index 0000000..545a076 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/installation/structure.md @@ -0,0 +1,116 @@ +--- +title: Project structure +description: Learn about the project structure and how to navigate it. +url: /docs/web/installation/structure +--- + +# Project structure + +The main directories in the project are: + +* `apps` - the location of the main apps +* `packages` - the location of the shared code and the API + +### `apps` Directory + +This is where the apps live. It includes web app (Next.js), mobile app (React Native - Expo), and the browser extension (WXT - Vite + React). Each app has its own directory. + +### `packages` Directory + +This is where the shared code and the API for packages live. It includes the following: + +* shared libraries (database, mailers, cms, billing, etc.) +* shared features (auth, mails, billing, ai etc.) +* UI components (buttons, forms, modals, etc.) + +All apps can use and reuse the API exported from the packages directory. This makes it easy to have one, or many apps in the same codebase, sharing the same code. + +## Repository structure + +By default the monorepo contains the following apps and packages: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +## Web application structure + +The web application is located in the `apps/web` folder. It contains the following folders: + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/.context/turbostarter-framework-context/sections/web/installation/update.md b/.context/turbostarter-framework-context/sections/web/installation/update.md new file mode 100644 index 0000000..709cd2b --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/installation/update.md @@ -0,0 +1,97 @@ +--- +title: Updating codebase +description: Learn how to update your codebase to the latest version. +url: /docs/web/installation/update +--- + +# Updating codebase + +If you've been following along with our previous guides, you should already have a Git repository set up for your project, with an `upstream` remote pointing to the original repository. + +Updating your project involves fetching the latest changes from the `upstream` remote and merging them into your project. Let's dive into the steps! + + + + ## Stash changes + + + If you don't have any changes to stash, you can skip this step and proceed with the update process. + + Alternatively, you can [commit](https://git-scm.com/docs/git-commit) your changes. + + + If you have any uncommitted changes, stash them before proceeding. It will allow you to avoid any conflicts that may arise during the update process. + + ```bash + git stash + ``` + + This command will save your changes in a temporary location, allowing you to retrieve them later. Once you're done updating, you can apply the stash to your working directory. + + ```bash + git stash apply + ``` + + + + ## Pull changes + + Pull the latest changes from the `upstream` remote. + + ```bash + git pull upstream main + ``` + + When prompted the first time, please opt for merging instead of rebasing. + + Don't forget to run `pnpm i` in case there are any updates in the dependencies. + + + + ## Resolve conflicts + + If there are any conflicts during the merge, Git will notify you. You can resolve them by opening the conflicting files in your code editor and making the necessary changes. + + + If you find conflicts in the `pnpm-lock.yaml file`, accept either of the two changes (avoid manual edits), then run: + + ```bash + pnpm i + ``` + + Your lock file will now reflect both your changes and the updates from the upstream repository. + + + + + ## Run a health check + + After resolving the conflicts, it's time to test your project to ensure everything is working as expected. Run your project locally and navigate through the various features to verify that everything is functioning correctly. + + For a quick health check, you can run: + + ```bash + pnpm lint + pnpm typecheck + + ``` + + If everything looks good, you're all set! Your project is now up to date with the latest changes from the `upstream` repository. + + + + ## Commit and push + + Once everything is working fine, don't forget to commit your changes using: + + ```bash + git commit -m "" + ``` + + and push them to your remote repository with: + + ```bash + git push origin + ``` + + diff --git a/.context/turbostarter-framework-context/sections/web/internationalization/configuration.md b/.context/turbostarter-framework-context/sections/web/internationalization/configuration.md new file mode 100644 index 0000000..47d9c5a --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/internationalization/configuration.md @@ -0,0 +1,136 @@ +--- +title: Configuration +description: Learn how to configure internationalization in TurboStarter. +url: /docs/web/internationalization/configuration +--- + +# Configuration + +The default global configuration is defined in the `@turbostarter/i18n` package and shared across all applications. You can override it in each app to customize the internationalization setup for that specific app. + +The configuration is defined in the `packages/i18n/src/config.ts` file: + +```ts title="packages/i18n/src/config.ts" +export const config = { + locales: ["en", "es"], + defaultLocale: "en", + namespaces: [ + "common", + "admin", + "organization", + "dashboard", + "auth", + "billing", + "marketing", + "validation", + ], + cookie: "locale", +} as const; +``` + +Let's break down the configuration options: + +* `locales`: An array of all supported locales. +* `defaultLocale`: The default locale to use if no other locale is detected. +* `namespaces`: An array of all namespaces used in the application. +* `cookie`: The name of the cookie to store the detected locale (acts as a cache). + +## Translation files + +The core of the whole internationalization setup is the translation files. They are stored in the `packages/i18n/src/translations` directory and are used to store the translations for each locale and namespace. + +Each directory represents a locale and contains a set of files, each corresponding to a specific namespace (e.g. `en/common.json`). Inside we define the keys and values for the translations. + +```ts title="packages/i18n/src/translations/en/common.json" +{ + "hello": "Hello, world!" +} +``` + +That way we can ensure that we have a single source of truth for the translations and we can use them consistently in all the applications. + +## Locales + +The `locales` array in the configuration defines the list of supported languages in your application. Each locale is represented by a string that uniquely identifies the language. + +To add a new locale, you need to: + +1. Add the new locale to the `locales` array in the configuration. +2. Create a new directory in the `packages/i18n/src/translations` directory. +3. Create a new file in the new directory for each namespace and add the translations for the new locale. + +For example, if you want to add the `fr` locale, you need to: + +1. Add `fr` to the `locales` array in the configuration. +2. Create a new directory in the `packages/i18n/src/translations` directory. +3. Create a new file for each namespace in the created directory and add the translations for the new locale. + +### Fallback locale + +The `defaultLocale` option in the configuration defines the fallback locale. If a translation is not found for a specific locale, the fallback locale will be used. + +We can also override this setting in each [app configuration](/docs/web/configuration/app) by configuring the `locale` property. + +## Namespaces + +`namespaces` are used to group translations by feature or module. This helps in organizing the translations and makes it easier to maintain them. + +### Why not one big namespace? + +Using multiple namespaces instead of one large namespace helps with: + +1. **Performance:** load translations on-demand instead of all at once, reducing the initial bundle size. +2. **Organization:** group translations by feature (e.g., `auth`, `common`, `dashboard`). +3. **Maintenance:** easier to update and manage smaller translation files. +4. **Development:** better TypeScript support and team collaboration. + +For example, you might structure your namespaces like this: + + + + ```ts title="packages/i18n/src/translations/en/common.json" + { + "hello": "Hello, world!" + } + ``` + + + + ```ts title="packages/i18n/src/translations/en/auth.json" + { + "login": "Login", + "register": "Register" + } + ``` + + + + ```ts title="packages/i18n/src/translations/en/billing.json" + { + "invoice": "Invoice", + "payment": "Payment", + "subscription": "Subscription" + } + ``` + + + +Remember that while you can create as many namespaces as needed, it's important to maintain a balance - too many namespaces can lead to unnecessary complexity, while too few might defeat the purpose of separation. + +## Routing + +TurboStarter implements locale-based routing by placing pages under the `[locale]` folder. However, the default locale (usually `en`) is not prefixed in the URL for better SEO and user experience. + +For example, with English as the default locale and Polish as an additional language: + +* `/dashboard` → English version (default locale) +* `/pl/dashboard` → Polish version + +The app also automatically detects the user's preferred language through cookies, HTML `lang` attribute, and browser's `Accept-Language` header. + +This ensures a seamless experience where users get content in their preferred language while maintaining clean URLs for the default locale. + + + You can override the locale by manually setting the cookie or by navigating to + a URL with a different locale prefix. + diff --git a/.context/turbostarter-framework-context/sections/web/internationalization/overview.md b/.context/turbostarter-framework-context/sections/web/internationalization/overview.md new file mode 100644 index 0000000..b731800 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/internationalization/overview.md @@ -0,0 +1,40 @@ +--- +title: Overview +description: Get started with internationalization in TurboStarter. +url: /docs/web/internationalization/overview +--- + +# Overview + +TurboStarter uses [i18next](https://www.i18next.com/) for internationalization, which is one of the most popular and mature (over 10 years of development!) i18n frameworks for JavaScript. + + + With i18next, you can easily translate your application into multiple + languages, handle complex pluralization rules, format dates and numbers + according to locale, and much more. The framework is highly extensible through + plugins and provides excellent TypeScript support out of the box. + + +You can read more about `i18next` package in the [official documentation](https://www.i18next.com/overview/getting-started). + +![i18next logo](/images/docs/i18next.jpg) + +## Getting started + +TurboStarter comes with `i18next` pre-configured and abstracted behind the `@turbostarter/i18n` package. This abstraction layer ensures that any future changes to the underlying translation library won't impact your application code. The internationalization setup is ready to use out of the box and includes: + +* Multiple language support out of the box +* Type-safe translations with generated types +* Automatic language detection +* Easy-to-use React hooks for translations +* Built-in number and date formatting +* Support for nested translation keys +* Pluralization handling + +To start using internationalization in your app, you'll need to: + +1. Configure your supported languages +2. Add translation files +3. Use translation hooks in your components + +Check out the following sections to learn more about each step: diff --git a/.context/turbostarter-framework-context/sections/web/internationalization/translations.md b/.context/turbostarter-framework-context/sections/web/internationalization/translations.md new file mode 100644 index 0000000..8d408ef --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/internationalization/translations.md @@ -0,0 +1,147 @@ +--- +title: Translating app +description: Learn how to translate your application to multiple languages. +url: /docs/web/internationalization/translations +--- + +# Translating app + +TurboStarter provides a flexible and powerful translation system that works seamlessly across your entire application. Whether you're working with React Server Components (RSC), client-side components, or server-side rendering, you can easily integrate translations to create a fully internationalized experience. + +The translation system supports: + +* **Server components (RSC)** for efficient server-side translations +* **Client components** for dynamic language switching +* **Server-side rendering** for SEO-friendly translated content + +## Server components (RSC) + +To get the translations in a server component, you can use the `getTranslation` method: + +```tsx +import { getTranslation } from "@turbostarter/i18n"; + +export default async function MyComponent() { + const { t } = await getTranslation(); + + return
{t("common:hello")}
; +} +``` + +There is also a possibility to use the [Trans](https://react.i18next.com/latest/trans-component) component, which could be useful e.g. for interpolating variables: + +```tsx +import { Trans } from "@turbostarter/i18n"; +import { withI18n } from "@turbostarter/i18n/with-i18n"; + +const Page = () => { + return }} />; +}; + +export default withI18n(Page); +``` + +Although, to make it available in the server component, you need to wrap it with the `withI18n` HOC. + +Given that server components are rendered in parallel, it's uncertain which one will render first. Therefore, it's crucial to initialize the translations before rendering the server component on each page/layout. + +## Client components + +For client components, you can use the `useTranslation` hook from the `@turbostarter/i18n` package: + +```tsx +"use client"; + +import { useTranslation } from "@turbostarter/i18n"; + +export default function MyComponent() { + const { t } = useTranslation(); + + return
{t("common:hello")}
; +} +``` + +That's the simplest way to get the translations in a client component. + +## Server-side + +In all other places (e.g. metadata, API routes, sitemaps etc.) you can use the `getTranslation` method to get the translations server-side: + +```ts +import { getTranslation } from "@turbostarter/i18n"; + +export const generateMetadata = async () => { + const { t } = await getTranslation(); + + return { + title: t("common:title"), + }; +}; +``` + +It automatically checks the user's preferred locale and uses the correct translation. + +## Language switcher + +TurboStarter ships with a language customizer component that allows you to switch between languages. You can import and use the `LocaleCustomizer` component and drop it anywhere in your application to allow users to change the language seamlessly. + +```tsx +import { LocaleCustomizer } from "@turbostarter/ui-web/i18n"; + +export default function MyComponent() { + return ; +} +``` + +The component automatically displays all languages configured in your i18n settings. When a user switches languages, it will: + +1. Update the URL to include the new locale prefix (e.g. `/es/dashboard`) +2. Store the selected locale in a cookie for persistence +3. Refresh translations across the entire application +4. Preserve the current page/route during the language switch + +This provides a seamless localization experience without requiring any additional configuration. + +## Best practices + +Here are some recommended best practices for managing translations in your application: + +* Use descriptive translation keys that follow a logical hierarchy + + ```ts + // ✅ Good + "auth.login.title"; + + // ❌ Bad + "loginTitleForAuth"; + ``` + +* Keep translations organized in separate namespaces/files based on features or sections + + ``` + translations/ + ├── en/ + │ ├── auth.json + │ └── common.json + └── pl/ + ├── auth.json + └── billing.json + ``` + +* Avoid hardcoding text strings - always use translation keys even for seemingly static content + +* Always provide a fallback language (usually English) for when translations are missing + +* Use pluralization and interpolation features when dealing with dynamic content + + ```ts + // Pluralization + t("items", { count: 2 }); // "2 items" + + // Interpolation + t("welcome", { name: "John" }); // "Welcome, John!" + ``` + +* Regularly review and clean up unused translation keys to keep files maintainable + +* Use TypeScript for type-safe translation keys diff --git a/.context/turbostarter-framework-context/sections/web/marketing/legal.md b/.context/turbostarter-framework-context/sections/web/marketing/legal.md new file mode 100644 index 0000000..22b6711 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/marketing/legal.md @@ -0,0 +1,98 @@ +--- +title: Legal pages +description: Learn how to create and update legal pages +url: /docs/web/marketing/legal +--- + +# Legal pages + +Legal pages are defined in the `apps/web/src/app/[locale]/(marketing)/legal` directory. + +TurboStarter comes with the following legal pages: + +* **Terms and Conditions**: to define the terms and conditions of your application +* **Privacy Policy**: to define the privacy policy of your application +* **Cookie Policy**: to define the cookie policy of your application + +For obvious reasons, **these pages are empty and you need to fill in the content.** + +## Content from CMS + +Content for legal pages are stored as [MDX](https://mdxjs.com/) files in [content collection](/docs/web/cms/content-collections) in `packages/cms/src/content/collections/legal` directory. + +Then it's parsed and rendered as a Next.js page under corresponding slug: + +```tsx title="apps/web/src/app/[locale]/(marketing)/legal/[slug]/page.tsx" +import { + CollectionType, + getContentItemBySlug, + getContentItems, +} from "@turbostarter/cms"; + +export default async function Page({ params }: PageParams) { + const item = getContentItemBySlug({ + collection: CollectionType.LEGAL, + slug: (await params).slug, + locale: (await params).locale, + }); + + if (!item) { + return notFound(); + } + + return ; +} + +export function generateStaticParams() { + return getContentItems({ collection: CollectionType.LEGAL }).items.map( + ({ slug, locale }) => ({ + slug, + locale, + }), + ); +} +``` + +As it's fully typesafe it also allows us to generate metadata for each page based on the frontmatter that you define in the MDX file: + +```tsx title="apps/web/src/app/[locale]/(marketing)/legal/[slug]/page.tsx" +export async function generateMetadata({ params }: PageParams) { + const item = getContentItemBySlug({ + collection: CollectionType.LEGAL, + slug: (await params).slug, + locale: (await params).locale, + }); + + if (!item) { + return notFound(); + } + + return getMetadata({ + title: item.title, + description: item.description, + })({ params }); +} +``` + +Read more about it in the [CMS section](/docs/web/cms/overview). + +## ChatGPT prompts + +Each `.mdx` file with legal content include a set of useful prompts that you can use to generate the content. + + + Please, be aware that **ChatGPT is not a lawyer** and the content generated by it should be reviewed by one before publishing. Take your time and treat the generated content as a starting point not a final document. + + +```mdx title="privacy-policy.mdx" +--- +title: Privacy Policy +description: Our privacy policy outlines how we collect, use, and protect your personal information. +--- + +{/* 💡 You can use one of the following ChatGPT prompts to generate this 💡 */} + +... +``` + +Feel free to add your own content or even additional pages to the `legal` collection. diff --git a/.context/turbostarter-framework-context/sections/web/marketing/pages.md b/.context/turbostarter-framework-context/sections/web/marketing/pages.md new file mode 100644 index 0000000..3e9cd63 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/marketing/pages.md @@ -0,0 +1,42 @@ +--- +title: Marketing pages +description: Discover which marketing pages are available out of the box and how to add a new one. +url: /docs/web/marketing/pages +--- + +# Marketing pages + +TurboStarter comes with pre-defined marketing pages to help you get started with your SaaS application. These pages are built with Next.js and Tailwind CSS and are located in the `apps/web/src/app/[locale]/(marketing)` directory. + +TurboStarter comes with the following marketing pages: + +* **Home**: conversions-optimized [landing page](https://demo.turbostarter.dev) with [hero section](https://demo.turbostarter.dev#hero), [features](https://demo.turbostarter.dev#features), [pricing](https://demo.turbostarter.dev#pricing), [testimonials](https://demo.turbostarter.dev#testimonials), [FAQ](https://demo.turbostarter.dev#faq) and more +* [Blog](/docs/web/cms/blog): to display your blog posts +* **Pricing**: to display your pricing plans +* **Contact**: to enable users to contact you with a contact form + +## Contact form + +To make the contact form work, you need to add the following environment variable: + +```dotenv +CONTACT_EMAIL= +``` + +Set this variable to the email address where you want to receive contact form submissions. The sender's email address will match what you configured in your [mailing configuration](/docs/web/emails/configuration). + +## Adding a new marketing page + +To add a new marketing page, create a new directory in `apps/web/src/app/[locale]/(marketing)` with the desired route name. + +The page will automatically become available in your application at the corresponding URL path. + +For example, to create a page accessible at `/about`, create a directory named `about` and add a `page.tsx` file inside it. The complete path would be `apps/web/src/app/[locale]/(marketing)/about/page.tsx`. + +```tsx title="apps/web/src/app/[locale]/(marketing)/about/page.tsx" +export default function AboutPage() { + return
About
; +} +``` + +This page inherits the layout at `apps/web/src/app/[locale]/(marketing)/layout.tsx`. You can customize the layout by editing this file - but remember that it will affect all marketing pages. diff --git a/.context/turbostarter-framework-context/sections/web/marketing/seo.md b/.context/turbostarter-framework-context/sections/web/marketing/seo.md new file mode 100644 index 0000000..81930b4 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/marketing/seo.md @@ -0,0 +1,188 @@ +--- +title: SEO +description: Learn how to optimize your app for search engines. +url: /docs/web/marketing/seo +--- + +# SEO + +SEO is an important part of building a website. It helps search engines understand your website and rank it higher in search results. In this guide, you'll learn how to improve your SaaS application's search engine optimization (SEO). + + + TurboStarter is already optimized for SEO out of the box (including meta tags, sitemaps, robots files and many more). However, there are a few things you can do to improve your application's SEO. + + +**Content:** High-quality, relevant content is the cornerstone of effective SEO. Focus on **creating valuable, engaging content** that addresses your customers' needs and questions. Regularly update your content to keep it fresh and relevant. + +**Keyword optimization:** Conduct thorough keyword research to identify terms your target audience is searching for. Incorporate these keywords naturally into your content, titles, meta descriptions, and headers. Avoid keyword stuffing; prioritize readability and user experience. + +**On-Page SEO:** + +* Use descriptive, keyword-rich titles and meta descriptions for each page. +* Implement a clear heading structure (H1, H2, H3) to organize your content. +* Optimize images with descriptive file names and alt text. +* Ensure your URLs are clean, descriptive, and include relevant keywords. + +**Technical SEO:** + +* Improve website loading speed by optimizing images, minifying CSS and JavaScript, and leveraging browser caching. +* Ensure your website is mobile-friendly and responsive across all devices. +* Implement schema markup to help search engines better understand your content. +* Use HTTPS to secure your website and boost search rankings. + +**User experience:** + +* Design an intuitive site structure and navigation to improve user engagement. +* Reduce bounce rates by creating compelling, easy-to-read content. +* Implement internal linking to guide users through your site and distribute page authority. + +**Link building:** + +* Create high-quality, shareable content to naturally attract backlinks. +* Engage in guest posting on reputable sites within your industry. +* Participate in industry forums and discussions, providing valuable insights and linking to your content when relevant. +* Leverage social media to increase content visibility and encourage sharing. + +**Local SEO (if applicable):** + +* Claim and optimize your Google My Business listing. +* Ensure consistent NAP (Name, Address, Phone) information across all online directories. +* Encourage customer reviews on Google and other relevant platforms. + +**Monitor and analyze:** + +* Use [Google Search Console](https://search.google.com/search-console/about) to monitor your site's performance in search results and identify issues. +* Regularly analyze your SEO efforts using tools like Google Analytics to understand user behavior and refine your strategy. + +**Stay updated:** + +* Keep abreast of SEO best practices and algorithm updates to continually refine your strategy. +* Regularly audit your website to identify and fix any SEO issues. + +## Sitemap + +Generally speaking, Google will find your pages without a sitemap as it follows the link in your website. However, you can add pages to the sitemap by adding them to the `apps/web/src/app/sitemap.ts` file, which is used to generate the sitemap. + +If you add more static pages to your website, you can add them to the sitemap by adding them to the `apps/web/src/app/sitemap.ts` returned array. + +```tsx title="sitemap.ts" +export default function sitemap(): MetadataRoute.Sitemap { + return [ + { + ...getEntry(pathsConfig.index), + lastModified: new Date(), + changeFrequency: "monthly", + priority: 1, + }, + ...getContentItems({ + collection: CollectionType.BLOG, + locale: appConfig.locale, + }).items.map((post) => ({ + ...getEntry(pathsConfig.marketing.blog.post(post.slug)), + lastModified: new Date(post.lastModifiedAt), + changeFrequency: "monthly", + priority: 0.7, + })), + + /* other pages */ + ]; +} +``` + +All the existing pages are already added to the sitemap. You don't need to add them manually. + +## Meta tags + +TurboStarter provides a helper function called `getMetadata` to easily set meta tags for your pages. This helper ensures consistent metadata formatting across your site and includes essential SEO tags like title, description, and Open Graph tags. You can use it in any page's metadata export: + +```tsx title="page.tsx" +export const generateMetadata = getMetadata({ + title: "My Page Title", + description: "My Page Description", +}); +``` + +This will generate the following meta tags: + +```html + + + +``` + +The `getMetadata` helper is really useful for generating consistent meta tags across your site, making SEO optimization simpler and more reliable. + + + `getMetadata` also supports translations. You can pass a translation key to the `title` and `description` parameters, and it will automatically use the correct translation for the current locale. + + ```tsx + export const generateMetadata = getMetadata({ + title: "billing:title", + description: "billing:description", + }); + ``` + + In this example, the `title` and `description` will be fetched from the `billing` namespace for the current locale and placed in the meta tags. + + +## Backlinks + +Backlinks are said to be the **most important factor** in modern SEO. The more backlinks you have from high-quality websites, the higher your website will rank in search results - and the more traffic you'll get. + +How do you acquire backlinks? The most effective strategy is to create high-quality, valuable content that naturally attracts links from other websites. However, there are several other methods to build backlinks: + +1. **Guest blogging:** Contribute articles to reputable websites within your industry. This not only provides backlinks but also exposes your brand to a new audience. +2. **Strategic outreach:** Identify websites that could benefit from linking to your content. Reach out with a personalized pitch, explaining the value your content adds to their audience. +3. **Digital PR:** Create newsworthy content or conduct original research that journalists and bloggers will want to reference and link to. +4. **Broken link building:** Find broken links on relevant websites and suggest your content as a replacement. +5. **Resource page link building:** Find resource pages in your niche and suggest your content for inclusion. +6. **Social media engagement:** While not directly impacting SEO, active social media presence can increase content visibility and indirectly lead to more backlinks. +7. **Create linkable assets:** Develop infographics, tools, or comprehensive guides that others in your industry will want to reference. +8. **Participate in industry forums and discussions:** Contribute meaningfully to conversations in your field, including your website when relevant. + +Remember, the quality of backlinks is more important than quantity. Focus on acquiring links from authoritative, relevant websites in your niche. Avoid any black-hat techniques or link schemes that could result in penalties from search engines. + +## Adding your website to Google Search Console + +Once you've optimized your website for SEO, you can add it to Google Search Console. Google Search Console is a free tool that helps you monitor and maintain your website's presence in Google search results. + +You can use it to check your website's indexing status, submit sitemaps, and get insights into how Google sees your website. + +The first thing you need to do is verify your website in Google Search Console. You can do this by adding a meta tag to your website's HTML or by uploading an HTML file to your website. + +Once you've verified your website, you can submit your sitemap to Google Search Console. This will help Google find and index your website's pages faster. + +Please submit your sitemap to Google Search Console by going to the `Sitemaps` section and adding the URL of your sitemap. The URL of your sitemap is `https://your-website.com/sitemap.xml`. + +Of course, please replace `your-website.com` with your actual website URL. + +## Content + +When it comes to internal factors, **content is king**. Make sure your content is relevant, useful, and engaging. Make sure it's updated regularly and optimized for SEO. + + + Most importantly, you want to think about how your customers will search for the problem your SaaS is solving. For example, if you're building a project management tool, you might want to write about project management best practices, how to manage a remote team, or how to use your tool to improve productivity. + + +You can use the blog and documentation features in TurboStarter to create high-quality content that will help your website rank higher in search results - and help your customers find what they're looking for. + +## Indexing and ranking take time + +New websites can take a while to get indexed by search engines. It can take anywhere from a few days to a few weeks (in some cases, even months!) for your website to show up in search results. Be patient and keep updating your content and optimizing your website for search engines. + +Also, you can edit `robots.ts` file to control which pages are indexed by search engines: + +```tsx title="robots.ts" +export default function robots(): MetadataRoute.Robots { + return { + rules: { + userAgent: "*", + allow: "/", + disallow: ["/api", "/dashboard", "/auth"], + }, + sitemap: appConfig.url + "/sitemap.xml", + }; +} +``` + +Remember, **SEO is an ongoing process.** Consistently apply these practices and adapt your strategy based on performance data and industry changes to improve your search engine visibility over time. diff --git a/.context/turbostarter-framework-context/sections/web/monitoring/overview.md b/.context/turbostarter-framework-context/sections/web/monitoring/overview.md new file mode 100644 index 0000000..7f71bbc --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/monitoring/overview.md @@ -0,0 +1,161 @@ +--- +title: Overview +description: Get started with web monitoring in TurboStarter. +url: /docs/web/monitoring/overview +--- + +# Overview + +TurboStarter includes lightweight monitoring hooks so you can quickly answer: **what's failing**, **where it's failing**, and **who it's affecting**. Out of the box, the web app can report exceptions from both the client and the server, and it's designed to be easy to extend with your preferred provider. + +## Capturing exceptions + +Monitoring starts with capturing exceptions reliably in the places that matter most: + +* **Client-side errors**: the Next.js App Router error boundary reports unexpected runtime errors so you get visibility without leaving users stuck on a broken screen. +* **Server-side errors**: API failures (for example, Hono errors in production) can be reported with a stable, anonymous distinct id so you can spot recurring issues and correlate them with sessions. +* **Manual reporting**: you can also report exceptions from your own `try/catch` blocks to add extra context around critical flows (payments, onboarding, imports, etc.). + + + + ```tsx + "use client"; + + import { captureException } from "@turbostarter/monitoring-web"; + + export default function ExampleComponent() { + const handleClick = () => { + try { + /* some risky operation */ + } catch (error) { + captureException(error); + } + }; + + return ; + } + ``` + + + + ```ts + import { captureException } from "@turbostarter/monitoring-web/server"; + + try { + /* do something */ + } catch (error) { + captureException(error); + } + ``` + + + + + Make sure to use the correct import for the `captureException` function. We're using the same name for both client and server monitoring, but they are different functions. For server-side, just add `/server` to the import path (`@turbostarter/monitoring-web/server`). + + + + ```tsx + import { captureException } from "@turbostarter/monitoring-web"; + ``` + + + + ```tsx + // [!code word:server] + import { captureException } from "@turbostarter/monitoring-web/server"; + ``` + + + + +## Identifying users + +Exception reports become dramatically more actionable once they're tied to a real user. TurboStarter automatically identifies signed-in users (based on the current auth session), which allows your monitoring provider to associate exceptions and sessions with a user profile. + +If you want richer debugging, identify users with traits (like email, plan, or role) so you can filter and segment issues by the people impacted. + +```tsx title="monitoring.tsx" +"use client"; + +import { useEffect } from "react"; +import { identify } from "@turbostarter/monitoring-web"; +import { authClient } from "~/lib/auth/client"; + +export const MonitoringProvider = ({ + children, +}: { + children: React.ReactNode; +}) => { + const session = authClient.useSession(); + + useEffect(() => { + if (session.isPending) { + return; + } + + identify(session.data?.user ?? null); + }, [session]); + + return <>{children}; +}; +``` + + + On the server, there are no dedicated identification helper. Most providers that support user-level tracking expect you to pass an identifier or traits directly within the `captureException` call (for example, as a `userId` or similar property), so make sure to check your specific provider's documentation for the recommended way to include user information. + + +## Providers + +The starter implements multiple providers for managing monitoring. To learn more about each provider and how to configure them, see their respective sections: + + + + + + + +Configuration and setup are handled for you via a unified API, making it easy to switch monitoring providers by just updating the exports. You can also add custom providers without disrupting any monitoring-related logic. + +## Best practices + +Below are some guidelines to keep monitoring useful, low-noise, and privacy-safe. + + + + Report unexpected exceptions and failed business-critical operations; avoid + logging “expected” states (validation errors, user cancellations, missing + optional data). + + + + Include what the user was doing (route/action), relevant IDs (request id, + order id), and a clear message so you can reproduce and triage quickly. + + + + Identify with stable IDs; only attach traits that are necessary for + debugging. Don’t send secrets or sensitive fields (tokens, passwords, raw + payment details). + + + + If a loop or retry can fire many times, guard your capture calls so you + don’t spam your provider (and your budget). + + + + Keep dev/staging/prod isolated (separate projects or environment tags) so + production alerts stay meaningful. + + + + Set alerts for spikes in error rate, degraded performance, and failures in + critical flows (auth, checkout, billing webhooks), not for every single + exception. + + + +Application monitoring helps you track errors, exceptions, and performance issues for better app reliability. With multiple provider support, you can quickly spot and resolve problems. + +Focus on actionable errors, useful context, and user privacy to get the most value from your monitoring. diff --git a/.context/turbostarter-framework-context/sections/web/monitoring/posthog.md b/.context/turbostarter-framework-context/sections/web/monitoring/posthog.md new file mode 100644 index 0000000..f019355 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/monitoring/posthog.md @@ -0,0 +1,153 @@ +--- +title: PostHog +description: Learn how to setup PostHog as your web monitoring provider. +url: /docs/web/monitoring/posthog +--- + +# PostHog + +[PostHog](https://posthog.com/) is a comprehensive product analytics platform that includes error tracking, session replay, feature flags, and more. It helps developers identify, diagnose, and fix issues in their applications by capturing and reporting errors and exceptions in real time. + +With features like automatic error reporting, stack trace visualization, and user/session context, PostHog provides deep insight into how your application is behaving in production so you can quickly resolve problems and improve reliability. + + + To use PostHog as your monitoring provider, you need to have an account. You can create one [here](https://app.posthog.com/signup) or [self-host](https://posthog.com/docs/self-host) it. + + + + PostHog is also one of pre-configured providers for [analytics](/docs/web/analytics/overview) in TurboStarter. You can learn more about it [here](/docs/web/analytics/configuration#posthog). + + +![PostHog banner](/images/docs/web/monitoring/posthog/banner.jpg) + +## Configuration + +PostHog integrates seamlessly with TurboStarter, enabling you to monitor application errors and performance from development to production. By configuring PostHog as your monitoring provider, you'll be able to detect, track, and resolve issues proactively, leading to a more stable and reliable app. + +Follow the simple setup instructions below to get started with PostHog in your TurboStarter project. + + + + ### Create a project + + First, you need to create a [project](https://app.posthog.com/project/settings) in PostHog. You can do it directly from your [dashboard](https://app.posthog.com) by clicking on the *New Project* button. + + + + ### Activate PostHog as your monitoring provider + + The monitoring provider to use is determined by the exports in `packages/monitoring/web` package. To activate PostHog as your monitoring provider, you need to update the exports in: + + + + ```ts + // [!code word:posthog] + export * from "./posthog"; + ``` + + + + ```ts + // [!code word:posthog] + export * from "./posthog/server"; + ``` + + + + ```ts + // [!code word:posthog] + export * from "./posthog/env"; + ``` + + + + If you want to customize the provider, you can find its definition in `packages/monitoring/web/src/providers/posthog` directory. + + + + ### Set environment variables + + Based on your [project settings](https://app.posthog.com/project/settings), fill the following environment variables in your `.env.local` file in `apps/web` directory and your deployment environment: + + ```dotenv title="apps/web/.env.local" + NEXT_PUBLIC_POSTHOG_KEY="your-posthog-api-key" + NEXT_PUBLIC_POSTHOG_HOST="https://us.i.posthog.com" + ``` + + + +That's it! You can now start your app and see the errors and exceptions in your [PostHog dashboard](https://app.posthog.com/project/error_tracking). + +![PostHog error](/images/docs/web/monitoring/posthog/error.png) + +Feel free to customize the configuration to your needs. For more information, please refer to the [PostHog documentation](https://posthog.com/docs/error-tracking/installation/nextjs). + + + + + + + +## Uploading source maps + +**Source maps** are files that map your minified or transpiled code (such as the JavaScript code generated by frameworks like Next.js) back to your original source code (for example, TypeScript or unbundled JavaScript). When your app is running in production, the code is often bundled and minified to improve performance, which makes stack traces and error messages hard to read and debug. + + + With source maps enabled and uploaded to your monitoring provider (like PostHog), error reports include references to the original lines of your source code, not just the processed/minified output. + + +PostHog can automatically provide readable stack traces for errors using source maps. The `@posthog/nextjs-config` package handles source map generation and upload automatically during the build process. + +To start using source maps, install the package `@posthog/nextjs-config` in `apps/web/package.json` as a dependency. + +```bash +pnpm i @posthog/nextjs-config --filter web +``` + +Next, extend your app's Next.js options by adding `withPostHogConfig` into the `next.config.ts` file: + +```ts title="apps/web/next.config.ts" +import { withPostHogConfig } from "@posthog/nextjs-config"; + +const config = { + /* existing Next.js configuration options */ +}; + +export default withPostHogConfig(config, { + personalApiKey: process.env.POSTHOG_API_KEY, + envId: process.env.POSTHOG_ENV_ID, + host: process.env.NEXT_PUBLIC_POSTHOG_HOST, + sourcemaps: { + enabled: true, // Enable sourcemaps generation and upload + project: "my-application", // Optional: Project name, defaults to repository name + version: "1.0.0", // Optional: Release version, defaults to current git commit + deleteAfterUpload: true, // Delete sourcemaps after upload, defaults to true + }, +}); +``` + +Make sure you have set the following environment variables locally and in your deployment environment: + +* `POSTHOG_ERROR_TRACKING_API_KEY` - Your [Personal API Key](https://app.posthog.com/settings/user-api-keys#variables) with write access on error tracking +* `POSTHOG_PROJECT_ID` - Project ID from [project settings](https://app.posthog.com/settings/environment#variables) +* `NEXT_PUBLIC_POSTHOG_HOST` - Your PostHog instance URL + + + Before proceeding, confirm that source maps are being generated by checking for `.js.map` files in your `dist` directory. These are the symbol sets that will be used to unminify stack traces in PostHog. + + Next, confirm that source maps are successfully uploaded to PostHog by checking the [symbol sets](https://app.posthog.com/project/settings/symbol-sets) section in your project settings. + + Finally, confirm that the served files are injected with the correct source map comment in production. You can do this by inspecting your deployed app in browser dev tools and looking for a comment like this at the end of your JavaScript bundles: + + ```js + //# chunkId=0197e6db-9a73-7b91-9e80-4e1b7158db5c + ``` + + +Once everything is set up, PostHog will provide you with detailed, easy-to-read error reports that link directly back to your original source code - even after your code has been bundled or minified. This makes diagnosing and fixing production issues much simpler. + + + + + + diff --git a/.context/turbostarter-framework-context/sections/web/monitoring/sentry.md b/.context/turbostarter-framework-context/sections/web/monitoring/sentry.md new file mode 100644 index 0000000..209030a --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/monitoring/sentry.md @@ -0,0 +1,157 @@ +--- +title: Sentry +description: Learn how to setup Sentry as your web monitoring provider. +url: /docs/web/monitoring/sentry +--- + +# Sentry + +[Sentry](https://sentry.io/) is a popular error monitoring and performance tracking platform. It helps developers identify, diagnose, and fix issues in their applications by capturing and reporting errors and exceptions in real time. + +With features like automatic error reporting, stack trace visualization, and user/session context, Sentry provides deep insight into how your application is behaving in production so you can quickly resolve problems and improve reliability. + + + To use Sentry as your monitoring provider, you need to have an account. You can create one [here](https://sentry.io/signup). + + +![Sentry banner](/images/docs/web/monitoring/sentry/banner.png) + +## Configuration + +Sentry integrates seamlessly with TurboStarter, enabling you to monitor application errors and performance from development to production. By configuring Sentry as your monitoring provider, you’ll be able to detect, track, and resolve issues proactively, leading to a more stable and reliable app. + +Follow the simple setup instructions below to get started with Sentry in your TurboStarter project. + + + + ### Create a project + + First, you need to create a [project](https://docs.sentry.io/product/projects/) in Sentry. You can do it directly from your [dashboard](https://sentry.io/settings/account/projects/) by clicking on the *Create Project* button. + + + + ### Activate Sentry as your monitoring provider + + The monitoring provider to use is determined by the exports in `packages/monitoring/web` package. To activate Sentry as your monitoring provider, you need to update the exports in: + + + + ```ts + // [!code word:sentry] + export * from "./sentry"; + ``` + + + + ```ts + // [!code word:sentry] + export * from "./sentry/server"; + ``` + + + + ```ts + // [!code word:sentry] + export * from "./sentry/env"; + ``` + + + + If you want to customize the provider, you can find its definition in `packages/monitoring/web/src/providers/sentry` directory. + + + + ### Set environment variables + + Based on your [project settings](https://sentry.io/project/settings), fill the following environment variables in your `.env.local` file in `apps/web` directory and your deployment environment: + + ```dotenv title="apps/web/.env.local" + NEXT_PUBLIC_SENTRY_DSN="your-sentry-dsn" + NEXT_PUBLIC_PROJECT_ENVIRONMENT="your-project-environment" + ``` + + + + ### Apply instrumentation to your app + + Install the package `@sentry/nextjs` in `apps/web/package.json` as a dependency. + + ```bash + pnpm i @sentry/nextjs --filter web + ``` + + Next, extend your app's Next.js options by adding `withSentryConfig` into the `next.config.ts` file: + + ```ts title="apps/web/next.config.ts" + import { withSentryConfig } from "@sentry/nextjs"; + + const config = { + /* existing Next.js configuration options */ + }; + + export default withSentryConfig(config, { + org: "your-sentry-org", + project: "your-sentry-project", + }); + ``` + + + +That's it! You can now start your app and see the errors and exceptions in your [Sentry dashboard](https://sentry.io/settings/account/projects/). + +![Sentry error](/images/docs/web/monitoring/sentry/error.jpg) + +Feel free to customize the configuration to your needs. For more information, please refer to the [Sentry documentation](https://docs.sentry.io/platforms/javascript/guides/nextjs/). + + + + + + + +## Uploading source maps + +**Source maps** are files that map your minified or transpiled code (such as the JavaScript code generated by frameworks like Next.js) back to your original source code (for example, TypeScript or unbundled JavaScript). When your app is running in production, the code is often bundled and minified to improve performance, which makes stack traces and error messages hard to read and debug. + + + With source maps enabled and uploaded to your monitoring provider (like Sentry), error reports include references to the original lines of your source code, not just the processed/minified output. + + +Sentry can automatically provide readable stack traces for errors using source maps, requiring a [Sentry auth token](https://docs.sentry.io/account/auth-tokens/). + +Update your `next.config.ts` file with the following options: + +```ts title="apps/web/next.config.ts" +import { withSentryConfig } from "@sentry/nextjs"; + +const config = { + /* existing Next.js configuration options */ +}; + +export default withSentryConfig(config, { + org: "your-sentry-org", + project: "your-sentry-project", + + // An auth token is required for uploading source maps. + authToken: process.env.SENTRY_AUTH_TOKEN, // [!code ++] + + // Upload a larger set of source maps for prettier stack traces (increases build time) + widenClientFileUpload: true, // [!code ++] +}); +``` + +Then, set the `SENTRY_AUTH_TOKEN` environment variable in your `.env.local` file in `apps/web` directory and your deployment environment: + +```dotenv title="apps/web/.env.local" +SENTRY_AUTH_TOKEN="your-sentry-auth-token" +``` + +With these steps, your Sentry integration will give you clear, actionable error reports tied directly to your source code - even after bundling and minification. This makes it much easier to debug and resolve production issues. + +Take a moment to test your setup and ensure source maps are correctly resolving stack traces in your [Sentry dashboard](https://sentry.io/settings/account/projects/). For deeper customization or additional troubleshooting, always consult the [official Sentry documentation](https://docs.sentry.io/platforms/javascript/guides/nextjs/sourcemaps/). + + + + + + diff --git a/.context/turbostarter-framework-context/sections/web/organizations/active-organization.md b/.context/turbostarter-framework-context/sections/web/organizations/active-organization.md new file mode 100644 index 0000000..6581804 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/organizations/active-organization.md @@ -0,0 +1,130 @@ +--- +title: Active organization +description: Set and switch the current organization context within your application. +url: /docs/web/organizations/active-organization +--- + +# Active organization + +The active organization is tracked based on the **URL slug** and the **session state**. We made it **as simple as possible** to use, introducing our custom hooks and an abstraction to sync it both ways. + +Below you can find more details about how to access the active organization across different contexts in your application. + +You can customize the behavior to your needs—for example, to restrict users to at most one organization at a time. + +## Server component + +You have two separate ways to access the active organization of the currently logged-in user on the server: + +* from the URL slug (organization-scoped routes) +* from the session (when no slug is present or you don't want to use it) + +We recommend always using the URL slug when you're doing something inside an organization-scoped route. This keeps the URL as the source of truth and works seamlessly with SSR and caching. + +```tsx title="page.tsx" +import { getOrganization } from "~/lib/auth/server"; + +export default async function Page({ + params, +}: { + params: Promise<{ + organization: string; + }>; +}) { + const organization = (await params).organization; + const activeOrganization = await getOrganization({ slug: organization }); + + return <>{activeOrganization?.name}; +} +``` + +Alternatively, you can use the session to access the active organization. This reads `session.activeOrganizationId` and resolves the organization by its stable ID. + +```tsx title="page.tsx" +import { getOrganization, getSession } from "~/lib/auth/server"; + +export default async function Page() { + const { session } = await getSession(); + const activeOrganization = await getOrganization({ + id: session.activeOrganizationId, + }); + + return <>{activeOrganization?.name}; +} +``` + +Be aware that sometimes you might encounter synchronization issues between the URL slug and the session, for example when a user opens multiple tabs to different organizations. More on this in the [Edge cases](#edge-cases) section. + +## Client component + +On the client side, we designed a dedicated hook to access the active organization - `useActiveOrganization`. It's a simple wrapper around the API that returns the active organization based on the URL slug or the session. It also helps keep the state in sync with the server session. + +```tsx title="client.tsx" +"use client"; + +import { useActiveOrganization } from "~/lib/hooks/use-active-organization"; + +export default function ClientComponent() { + const { activeOrganization, activeMember } = useActiveOrganization(); + + return ( + <> +

{activeOrganization?.name}

+

{activeMember?.role}

+ + ); +} +``` + +Using the hook is recommended over direct API calls, as it will keep the state in sync with the server session. + +It also returns the active member of the active organization, so you can access the user's role and other member-specific data. + +## API route + +To access the active organization data in an API route, you can read it from the session that is appended to the context when you use [authentication middleware](/docs/web/api/protected-routes). + +```ts title="action/router.ts" +export const actionRouter = new Hono().post("/", enforceAuth, async (c) => { + const organizationId = c.var.user.activeOrganizationId; + const organization = await getOrganization({ id: organizationId }); + return c.json(organization); +}); +``` + +Although it's the simplest way, we recommend directly passing the `organizationId` together with the payload when you need to perform an action. + +```ts title="action/router.ts" +export const actionRouter = new Hono().post( + "/", + enforceAuth, + validate( + "json", + z.object({ + organizationId: z.string(), + /* rest of the payload */ + }), + ), + async (c) => { + const { organizationId, ...payload } = c.req.valid("json"); + const organization = await getOrganization({ id: organizationId }); + return c.json(await performAction(organization, payload)); + }, +); +``` + +This ensures that the action is performed on the correct organization, even if the user has multiple organizations open in different tabs. See [Edge cases](#edge-cases) for more details. + +## Edge cases + +* **Expected and harmless:** Short periods where the URL slug and server session differ can happen (for example, with multiple tabs or quick switching). The active tab always treats the slug as the source of truth and the session catches up. +* **Multiple tabs:** Each tab maintains its own org context from its slug. As you switch focus, the shared session updates; brief divergence is normal and safe. +* **Rapid switching/slow network:** During fast navigation or poor connectivity, you may momentarily see the previous org while the session updates. Show a small loading state; cancel in-flight requests tied to the old org. +* **Missing/invalid slug:** If the slug is missing or invalid, we fall back to the session’s `activeOrganizationId` or redirect to a safe default. +* **Access or permission changes:** If a user loses access to the org they’re viewing, the data is cleared from the session and the user is redirected to a valid organization or personal dashboard. + + + Whenever the active organization changes, the server session is updated and the client is redirected to the new organization scope. + + All caches keyed by organization are invalidated to avoid leaking data between organizations. + diff --git a/.context/turbostarter-framework-context/sections/web/organizations/data-model.md b/.context/turbostarter-framework-context/sections/web/organizations/data-model.md new file mode 100644 index 0000000..08c26fc --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/organizations/data-model.md @@ -0,0 +1,93 @@ +--- +title: Data model +description: Entities and relationships for organizations and multi-tenancy. +url: /docs/web/organizations/data-model +--- + +# Data model + +Our multi-tenant model is organized around the concept of an **organization**. An organization represents a single tenant and is the primary boundary for data isolation, access control, and routing. + +Users can belong to multiple organizations through a membership. Invitations let organization admins onboard new members by email with a specific role. + + + +## Entities + +### Organization + +The tenant. Stores human-friendly `name`, unique `slug` (used in URLs and lookups), optional `logo`, and optional `metadata` for extensibility (feature flags, billing context, UI preferences, etc.). `createdAt` provides auditability. The `slug` is globally unique to keep URLs stable and predictable. + +### User + +The identity of a person. Users are global and can join many organizations. Account-level fields (e.g., `name`, `email`, verification, avatar, security flags) live here. + + + A user's application-wide properties (like a global `role` or moderation flags) are distinct from their per-organization role. + + +### Member (Membership) + +The join between a `user` and an `organization`. This is where multi-tenancy permissions are enforced. Each membership stores the `role` the user holds in that specific organization (for example, `member`, `admin`). + +Memberships include timestamps for auditing and can be cascaded when a user or organization is removed. + +### Invitation + +Represents an invite to join an organization by `email` with an intended `role`. It includes `status` (e.g., pending, accepted, revoked), `expiresAt`, and `inviterId` for traceability. + +On acceptance, an invitation creates a corresponding membership if one does not already exist. + +## Relationships and constraints + + + + Users and organizations are related many-to-many through memberships. A user + can join multiple organizations; an organization has multiple members. + + + + We keep `organization.slug` unique across the system to ensure + consistent routing and discoverability. Within a single organization, each + `userId` should only appear once in memberships; enforce this + at the application layer or with a composite unique index + `(organizationId, userId)`. + + + + * Deleting an organization removes its dependent memberships and invitations. + * Deleting a user removes their memberships and invitations. + + These cascades preserve referential integrity and prevent orphaned records. + + + +## Tenancy and isolation + +### Tenant separator + +`organizationId` is the tenant key. All tenant-scoped data should either live under the organization or reference it directly. Every read/write path in the application should be constrained by the current `organizationId`. + +### Query guardrails + +Derive the active `organizationId` from authenticated context (session or URL slug → lookup → id). Apply `organizationId` filters at the repository/service layer to avoid cross‑tenant reads. Add composite indexes that include `organizationId` on frequently queried relations. + +### Isolation level + +All organizations share the same database and schema, separated by `organizationId`. This keeps operations simple and cost‑effective. If stricter isolation is needed, evolve toward schema‑per‑tenant or database‑per‑tenant with care, as operational overhead increases. + + + The term "organizations" is used throughout the starter kit to identify a group of users. However, depending on your application's needs, you might want to represent these groups with a different name, such as "Teams" or "Workspaces." + + If that's the case, we suggest retaining "organization" as the internal term within your codebase (to avoid the complexity of renaming it everywhere), while customizing the UI labels to your preferred terminology. To do this, simply update all user-facing instances of "Organization" in your interface to reflect the term that best fits your application. + + +## Lifecycle flows + +* **Create organization**: Create an organization (with `name`, `slug`, optional `logo`/`metadata`) and immediately create a membership for the creator with an elevated role (commonly `owner`). +* **Invite member**: + 1. Admin creates an invitation specifying `email` and intended `role`. + 2. The invite is sent by email with an expiring token. + 3. On acceptance, if the user exists they are added as a member; otherwise they register and then join. + 4. Handle idempotency so repeated accepts don’t duplicate memberships. +* **Leave or remove**: Members can leave an organization and admins can remove members. The policy that "at least one owner must remain" is enforced at the application layer. diff --git a/.context/turbostarter-framework-context/sections/web/organizations/invitations.md b/.context/turbostarter-framework-context/sections/web/organizations/invitations.md new file mode 100644 index 0000000..ebc0080 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/organizations/invitations.md @@ -0,0 +1,92 @@ +--- +title: Invitations +description: Send, track, and accept organization invites. +url: /docs/web/organizations/invitations +--- + +# Invitations + +You can invite teammates **by email** to join an organization straight from your organization settings. + +Acceptance is frictionless: we verify the invite, create (or reuse) the membership with the intended role, and activate the organization in the user's session. + +The implementation is based on the [Better Auth plugin](https://www.better-auth.com/docs/plugins/organization) and designed to drive engagement, minimize back-and-forth and keep admins in control. + +![Invitations list](/images/docs/web/organizations/invitations/list.png) + +## Model + +As we can see inside our [data model](/docs/web/organizations/data-model), an invitation targets an `email`, carries the intended `role`, records the `inviterId`, and is scoped to an `organizationId`. + +```ts +export const invitation = pgTable("invitation", { + id: text().primaryKey(), + organizationId: text() + .notNull() + .references(() => organization.id, { onDelete: "cascade" }), + email: text().notNull(), + role: text(), + status: text().default("pending").notNull(), + inviterId: text() + .notNull() + .references(() => user.id, { onDelete: "cascade" }), + createdAt: timestamp().defaultNow().notNull(), + expiresAt: timestamp().notNull(), +}); +``` + +The invitations expire at `expiresAt` to keep links short‑lived. + +## Status + +An invitation can be in one of three states: + +* **Pending**: created/sent, awaiting acceptance. +* **Accepted**: verified; membership created or reused. +* **Rejected**: manually invalidated or removed via cascades. + + + Expiration is controlled by `expiresAt` (not a separate status). After this timestamp, the link is invalid and should be resent. + + +## Flow + +1. Admin creates an invite with `email` and `role`. The `organizationId` is inferred from the context. +2. System generates a signed, single-use token bound to the invite and `expiresAt` and sends a CTA link. +3. Recipient opens the link; we verify the token and email. +4. On success, we proceed to acceptance. + +## Onboarding + +### Existing user + +After verification, we create (or reuse) a membership with the invited role and set the active organization in the session. +![Join organization prompt](/images/docs/web/organizations/invitations/join.png) + +### New user + +We attach the invite context to signup; after registration, we create the membership and activate the organization - no detours required. +![Invitation disclaimer](/images/docs/web/organizations/invitations/sign-in-disclaimer.png) + +You can fully customize the invitation flow to fit your organization's needs. For example, you can add extra onboarding steps, capture additional user information, or implement advanced verification logic as part of the invite process. + +The system is designed to be extensible—tailor it to match your team's requirements and user experience preferences. + +## Automatic invalidation + +An invitation is automatically revoked in the following scenarios: + +* **The user accepts the invitation:** Once accepted, the token becomes invalid. +* **The user changes their email address:** To prevent misuse, any changes to the associated email automatically invalidate the token. +* **The user deletes their account:** Invitations linked to a deleted account are revoked to maintain data integrity. + +This ensures that invitations remain secure and aligned with the current state of user accounts. + +## Invitation management + +Admins of the organization and [super admins](/docs/web/admin/overview) can manage invitations via a dedicated section in the dashboard, where they can: + +* View the status of all invitations (`pending`, `accepted`, `rejected`). +* Resend invitations who did not respond. +* Revoke invitations if they were sent to the wrong email or are no longer needed. +* Adjust the role of an invitation if not yet accepted diff --git a/.context/turbostarter-framework-context/sections/web/organizations/overview.md b/.context/turbostarter-framework-context/sections/web/organizations/overview.md new file mode 100644 index 0000000..c8fc7de --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/organizations/overview.md @@ -0,0 +1,79 @@ +--- +title: Overview +description: Learn how to use organizations/teams/multi-tenancy in TurboStarter. +url: /docs/web/organizations/overview +--- + +# Overview + +Organizations let you build teams and multi-tenant SaaS out of the box, which is a widely used pattern, especially in a [B2B](https://en.wikipedia.org/wiki/Business-to-business) apps. Users can create organizations, invite teammates, assign roles, and seamlessly switch between workspaces. + + + [Multi-tenancy](https://www.ibm.com/think/topics/multi-tenant) is a software architecture pattern where a single instance of an application serves multiple tenants, each with its own data and configuration. + + +The feature is mostly powered by the [Better Auth organization plugin](https://www.better-auth.com/docs/plugins/organization) and integrates with TurboStarter's API, routing, data layer, and UI components. This allows you to share most of the code between the web app, [mobile app](/docs/mobile/organizations/overview), and [extension](/docs/extension/organizations). + + + +## Architecture + +TurboStarter uses a pragmatic multi-tenant architecture: + +* **Tenant context** lives in the session as the active organization ID (derived from the user's selection or defaults). Server handlers read this context to enforce scoping. +* **Data scoping** is performed via `organizationId` on tenant-owned tables and guard clauses in queries. Background tasks and API routes receive the same context. +* **Authorization** combines tenant scoping with role checks. We separate “can access this tenant?” from “can perform this action within the tenant?”. +* **Extensibility**: add new tenant-bound entities by including `organizationId` and using the provided helpers to read the active organization. + +This keeps data isolated per organization while remaining simple to reason about and customize. + + + You can restrict who can create organizations, perform actions within it, and hook into + lifecycle events using our API. + + Check dedicated [Data model](/docs/web/organizations/data-model), [RBAC](/docs/web/organizations/rbac) and [Invitations](/docs/web/organizations/invitations) sections or direct [Better Auth docs](https://www.better-auth.com/docs/plugins/organization) for more details. + + +## Concepts + +To effectively use multi-tenancy in your app, we introduced a few core concepts that define how the whole system works: + +| Concept | Description | +| ----------------------- | ----------------------------------------------------------------------------------------------- | +| **Organization** | A workspace that owns resources and settings, acting as an isolated tenant. | +| **Member** | A user assigned to an organization. | +| **Role** | Access level within an organization (see [RBAC](/docs/web/organizations/rbac)). | +| **Invitation** | Email request to join an organization (see [Invitations](/docs/web/organizations/invitations)). | +| **Active organization** | The currently selected organization in a user's session, used to scope data and permissions. | + +These concepts provide the building blocks for flexible team management and secure, multi-tenant SaaS applications. + +## Development data + +In development, TurboStarter automatically [seeds](/docs/web/installation/commands#seeding-database) some example data when you [setup services](/docs/web/installation/commands#setting-up-services): + +* One organization is created by default. +* All default roles are created and assigned within that organization. +* Sample invitations are generated so you can test the invite flow. + +You can safely experiment with these sample organizations, roles, and invitations to understand multi-tenancy features - [reset](/docs/web/installation/commands#resetting-database) or [reseed](/docs/web/installation/commands#seeding-database) anytime to return to the default state. + +The default credentials for demo users can be customized using the `SEED_EMAIL` and `SEED_PASSWORD` environment variables. + + + The default development data and setup are intended for local development and + testing only. **Never** use these seeds or configurations in a production + environment - they are insecure and may expose sensitive functionality. + + +## Customization + +You have flexibility to adapt organizations to fit your product. For example, you might rename labels (such as Organization to *Team* or *Workspace*), and update the UI copy accordingly. + +You can adjust the available [roles and permissions](/docs/web/organizations/rbac) to suit your access model. + +The [invitation flow](/docs/web/organizations/invitations) can be customized, including how verification, onboarding, or metadata capture work. + +You may also want to introduce tenant-specific policies, like usage limits, feature flags, or billing rules. + +Feel free to check how to configure all of these features in the dedicated sections below. diff --git a/.context/turbostarter-framework-context/sections/web/organizations/rbac.md b/.context/turbostarter-framework-context/sections/web/organizations/rbac.md new file mode 100644 index 0000000..7c8611b --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/organizations/rbac.md @@ -0,0 +1,97 @@ +--- +title: RBAC (Roles & Permissions) +description: Manage roles, permissions, and access scopes. +url: /docs/web/organizations/rbac +--- + +# RBAC (Roles & Permissions) + +Role-based access control (RBAC) lets you define who can do what in an organization. + + + If you're new to the RBAC concept, a simple mental model is: + + * Users belong to organizations. + * Users get roles. + * Roles map to permissions on resources. + + +In TurboStarter, we primarily rely on the [Better Auth plugin](https://www.better-auth.com/docs/plugins/organization) for the heavy lifting - roles, permissions, teams, and member management - while handling critical logic with our own code. + +This provides a flexible access control system, letting you control user access based on their role in the organization. You can also define custom permissions per role. + + + TurboStarter ships with the default RBAC system configured out of the box. This setup may be enough if you're not planning a very complex access control system, but you can also easily customize it to your needs. + + It also includes [protecting routes](/docs/web/api/protected-routes) that users with specific roles can access by adding custom middlewares and disabling certain actions in the UI. + + +## Roles + +Roles are named bundles of permissions. Keep them few and well-defined. By default, we have the following roles: + +```ts +const MemberRole = { + MEMBER: "member", + ADMIN: "admin", + OWNER: "owner", +} as const; +``` + +A user can have multiple roles in an organization. For example, a user can be a member and an admin (if it makes sense for your application). + + + The organization's `admin` role is **different** from the user's global `admin` role. + + The organization `admin` governs permissions only inside the organization, whereas the global `admin` controls access to the [super admin dashboard](/docs/web/admin/overview). + + +To create additional roles with custom permissions, see the [official documentation](https://www.better-auth.com/docs/plugins/organization#create-access-control) for more details. + +## Permissions + +Permissions represent what actions a role can perform on which resources. To check if the current user has permission to perform an action, you can use the `hasPermission` function. + +```ts +const canCreateProject = await authClient.organization.hasPermission({ + permissions: { + project: ["create"], + }, +}); +``` + +Or, if you're performing the check on the server, you can use the `hasPermission` function from the `auth.api` object. + +```ts +await auth.api.hasPermission({ + headers: await headers(), + body: { + permissions: { + project: ["create"], // This must match the structure in your access control + }, + }, +}); +``` + +Once your roles and permissions are defined, you can avoid server checks (e.g., to reduce API calls) by using the client-side `checkRolePermission` function. + +```ts +const { activeMember } = useActiveOrganization(); + +const canUpdateProject = authClient.organization.checkRolePermission({ + permission: { + project: ["update"], + }, + role: activeMember.role, +}); +``` + +We leverage the existing custom hook to retrieve the active member role within the [active organization](/docs/web/organizations/active-organization) context. That way, you can easily check whether a member has permission to perform an action without a server round trip. + + + This does not include any dynamic roles or permissions because everything runs synchronously on the client-side. Use the `hasPermission` APIs to include checks for dynamic roles and permissions. + + +If you need to add more granular permissions to existing roles, or create new ones, use the [`createAccessControl`](https://www.better-auth.com/docs/plugins/organization#custom-permissions) API. + +For further customization - such as dynamic access control, lifecycle hooks, or team management - see the guidance in the [official documentation](https://www.better-auth.com/docs/plugins/organization). diff --git a/.context/turbostarter-framework-context/sections/web/recipes/supabase.md b/.context/turbostarter-framework-context/sections/web/recipes/supabase.md new file mode 100644 index 0000000..fc605c0 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/recipes/supabase.md @@ -0,0 +1,221 @@ +--- +title: Supabase +description: Learn how to set up Supabase for your TurboStarter project. +url: /docs/web/recipes/supabase +--- + +# Supabase + +[Supabase](https://supabase.com) is an open-source backend platform built on top of PostgreSQL that provides a managed database, storage, and other features out of the box. + +You can adopt Supabase incrementally - start with just the pieces you need (for example, database only, or database + storage) and add more features over time. There's no requirement to integrate everything at once. + +In this guide, we'll walk you through the process of setting up Supabase as a provider for your TurboStarter project. This could include using it as a [database](https://supabase.com/docs/guides/database), [storage](https://supabase.com/docs/guides/storage), [edge runtime for your API](https://supabase.com/docs/guides/functions) and more. + +## Prerequisites + +Before you start, make sure you have: + +* **TurboStarter project** cloned locally with dependencies installed (you can use our [CLI](/docs/web/cli) to create a new project in seconds) +* **Supabase account** - you can create one at [supabase.com](https://supabase.com/sign-up) +* Basic familiarity with the core database docs: + * [Database overview](/docs/web/database/overview) + * [Migrations](/docs/web/database/migrations) + * [Database client](/docs/web/database/client) + + + + ## Create a new Supabase project + + 1. Go to the [Supabase dashboard](https://supabase.com). + 2. Create a **new project** (choose a strong database password and a region close to your users). + 3. Supabase will automatically provision a **PostgreSQL database** for you. + + ![Create a new Supabase project](/images/docs/web/recipes/supabase/create-project.png) + + Optionally, you can customize the **Security options** by choosing the **Only Connection String** option - it will opt out of autogenerating API for tables inside your database. It's not needed for TurboStarter setup, but of course you can still leverage it for your custom use-cases. + + ![Security options](/images/docs/web/recipes/supabase/security-options.png) + + Once the project is ready, you can fetch the connection string. + + + + ## Get the database connection string + + In the Supabase dashboard: + + 1. Open your project. + 2. Click on the **Connect** button at the top. + 3. Locate the **connection string** for your chosen ORM (it will be under the **ORMs** tab). + + ![Connect application](/images/docs/web/recipes/supabase/connect-app.png) + + Copy this value - you'll use it as your `DATABASE_URL`. + + + In your Supabase connection string, you can see a placeholder like `[YOUR-PASSWORD]`. Make sure to replace this with the actual password you set when creating your Supabase project. + + + + + ## Configure environment variables + + TurboStarter reads database connection settings from the **root** `.env.local` file and uses them inside the `@turbostarter/db` package. + + Create (or update) the `.env.local` file in the **monorepo root**: + + ```dotenv title=".env.local" + DATABASE_URL="postgres://postgres.[YOUR-PROJECT-REF]:[YOUR-PASSWORD]@aws-0-[aws-region].pooler.supabase.com:6543/postgres?pgbouncer=true&connection_limit=1" + ``` + + Replace: + + * `YOUR-PROJECT-REF` with your Supabase project ref + * `YOUR-PASSWORD` with the database password you set when creating the project + * `aws-region` with the region shown in the Supabase connection string + + + These variables are validated in the `@turbostarter/db` package and used to create Drizzle client for your database. + + + For more background on how `DATABASE_URL` is used, see [Database overview](/docs/web/database/overview). + + + + ## Setup your Supabase database + + With `DATABASE_URL` now pointing to Supabase, you can apply the existing TurboStarter schema to your Supabase database. + + From the monorepo root, run: + + ```bash + pnpm with-env pnpm --filter @turbostarter/db db:migrate + ``` + + This will: + + * Use your Supabase `DATABASE_URL` from `.env.local` + * Run all pending SQL migrations from `packages/db/migrations` + * Create the full TurboStarter schema (users, billing, demo tables, etc.) in Supabase + + If you're actively iterating on the schema, you can generate new migrations and apply them as described in [Migrations](/docs/web/database/migrations). + + + After running your migrations, you may want to seed your database with initial data (such as demo users or organizations). You can do this by running the following command: + + ```bash + pnpm with-env pnpm turbo db:seed + ``` + + This will populate your Supabase database with some example data you can use to test your application. + + + + + ## Use Supabase Storage as S3-compatible storage + + TurboStarter's storage layer is designed to work seamlessly with **any S3-compatible provider**. In this section, we'll show how to use [Supabase Storage](/docs/web/storage/overview) as your application's file storage back-end. + + Supabase Storage provides a simple, S3-compatible API and is a great choice if you're already using Supabase for your database. + + ### Create a storage bucket + + 1. In the Supabase dashboard, go to **Storage → Buckets**. + 2. Click **Create bucket** (name it whatever you want, for example `avatars` or `uploads`). + 3. Adjust settings based on your needs (e.g. limit the maximum file size, specify the allowed file types, etc.) + + ![Create a new bucket](/images/docs/web/recipes/supabase/create-bucket.png) + + You can create multiple buckets (for documents, images, videos, etc.) if needed. + + ### Generate S3 access keys in Supabase dashboard + + 1. Go to **Storage → S3 → Access keys**. + 2. Click **New access key**. + 3. Give it a descriptive name and create the key. + 4. Copy the **Access key ID** and **Secret access key** to use in your application. + + ![Generate S3 access keys](/images/docs/web/recipes/supabase/s3-keys.png) + + ### Configure S3 environment variables for Supabase Storage + + In your weba application's `.env.local`, add (or update) the S3 configuration used by TurboStarter's storage layer: + + ```dotenv title=".env.local" + S3_REGION="us-east-1" + S3_BUCKET="avatars" + S3_ENDPOINT="https://[YOUR-PROJECT-REF].supabase.co/storage/v1/s3" + S3_ACCESS_KEY_ID="your-access-key-id" + S3_SECRET_ACCESS_KEY="your-secret-access-key" + ``` + + These variables integrate directly with the storage configuration described in: + + * [Storage overview](/docs/web/storage/overview) + * [Storage configuration](/docs/web/storage/configuration) + + Once set, existing TurboStarter file upload flows (e.g. user avatars, organization logos) will use Supabase Storage via presigned URLs. + + + + ## Run your API on Supabase Edge Functions + + As we're using a [Hono](https://hono.dev) as our API server, you can deploy it as a Supabase Edge Function so it runs close to your users. + + At a high level: + + 1. Install the [Supabase CLI](https://supabase.com/docs/guides/cli) and initialize a Supabase project locally with `supabase init`. + 2. Create a new [Edge Function](https://supabase.com/docs/guides/functions/quickstart) (for example `hono-backend`) with `supabase functions new hono-backend`. + 3. Inside the generated function (for example `supabase/functions/hono-backend/index.ts`), set up a basic Hono app and export it via `Deno.serve(app.fetch)`: + + ```ts + import { Hono } from "jsr:@hono/hono"; + + // change this to your function name + const functionName = "hono-backend"; + const app = new Hono().basePath(`/${functionName}`); + + app.get("/hello", (c) => c.text("Hello from hono-server!")); + + Deno.serve(app.fetch); + ``` + + 4. Run the function locally with `supabase start` and `supabase functions serve --no-verify-jwt`, then call it from your TurboStarter app using the local or deployed function URL. + 5. When you're ready, deploy the function with `supabase functions deploy` (or `supabase functions deploy hono-backend`) and manage it using the Supabase dashboard, as described in the [Supabase Edge Functions docs](https://supabase.com/docs/guides/functions). + + This is entirely optional, but it's a great fit for lightweight APIs, webhooks, and other serverless logic you want to run alongside your Supabase project. + + + + ## Explore additional Supabase features + + Supabase is a full Postgres development platform, so beyond the database and storage pieces wired up above you can gradually add more features as your app grows ([see the Supabase homepage](https://supabase.com/) for an overview). + + Some features that fit especially well with TurboStarter's design are: + + * [Realtime](https://supabase.com/docs/guides/realtime) - built on [Postgres replication](https://www.postgresql.org/docs/current/runtime-config-replication.html), so you can stream changes from your existing TurboStarter tables (inserts, updates, deletes) into live UIs without changing how you manage schema or RLS. You still define tables and policies via `@turbostarter/db`, and opt into Realtime on top. + * [Vector](https://supabase.com/docs/guides/vector) - powered by the [pgvector](https://github.com/pgvector/pgvector) extension and stored in regular Postgres tables, making it easy to integrate semantic search or AI features while keeping everything in the same migrations and Drizzle models you already use in TurboStarter. We're using it extensively in our dedicated [AI Kit](/ai). + * [Cron](https://supabase.com/docs/guides/functions/cron) - enables you to schedule background jobs and periodic tasks with [pg\_cron](https://github.com/citusdata/pg_cron). You can define cron jobs for things like scheduled database cleanups, sending emails, report generation, or any recurring logic, all managed alongside your TurboStarter app with full Postgres integration. + + Because these features are all layered on top of Postgres, you can introduce them incrementally and keep managing everything through your familiar workflow. + + + + ## Start the development server + + With the database and other services configured to use Supabase, you can start TurboStarter as usual from the monorepo root: + + ```bash + pnpm dev + ``` + + TurboStarter will now: + + * Use **Supabase Postgres** as your database through `DATABASE_URL` + * Use **Supabase Storage** as your file storage through the S3-compatible endpoint + * Leverage **Supabase Edge Functions** (for example, with Hono) for your serverless backend + + + +That's it! You can now start building your application with Supabase as your main provider. Explore the [Supabase documentation](https://supabase.com/docs) for more features and best practices. diff --git a/.context/turbostarter-framework-context/sections/web/stack.md b/.context/turbostarter-framework-context/sections/web/stack.md new file mode 100644 index 0000000..7950134 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/stack.md @@ -0,0 +1,72 @@ +--- +title: Tech Stack +description: A detailed look at the technical details. +url: /docs/web/stack +--- + +# Tech Stack + +## Turborepo + +[Turborepo](https://turbo.build/) is a monorepo tool that helps you manage your project's dependencies and scripts. We chose a monorepo setup to make it easier to manage the structure of different features and enable code sharing between different packages. + +} /> + +## Next.js + +[Next.js](https://nextjs.org) is one of the most popular [React](https://react.dev) frameworks that enables server-side rendering, static site generation, and more. We chose Next.js for its flexibility and ease of use. We're also using it to host our serverless API. + + + } /> + + } /> + + +## Hono & React Query + +[Hono](https://hono.dev) is a small, simple, and ultrafast web framework for the edge. It provides tools to help you build APIs and web applications faster. It includes an RPC client for making type-safe function calls from the frontend. We use Hono to build our serverless API endpoints. + +To make data fetching and caching from our API easy and reliable, we pair Hono with [React Query](https://tanstack.com/query/latest). It helps manage asynchronous data, caching, and state synchronization between the client and backend, delivering a fast and seamless UX. + + + } /> + + + } + /> + + +## Better Auth + +[Better Auth](https://www.better-auth.com) is a modern authentication library for fullstack applications. It provides ready-to-use snippets for features like email/password login, magic links, OAuth providers, and more. We use Better Auth to handle all authentication flows in our application. + +} /> + +## Tailwind CSS + +[Tailwind CSS](https://tailwindcss.com) is a utility-first CSS framework that helps you build custom designs without writing any CSS. We also use [Radix UI](https://radix-ui.com) for our headless components library and [shadcn/ui](https://ui.shadcn.com), which enables you to generate pre-designed components with a single command. + + + } /> + + } /> + + } /> + + +## Drizzle + +[Drizzle](https://orm.drizzle.team/) is a super fast [ORM](https://orm.drizzle.team/docs/overview) (Object-Relational Mapping) tool for databases. It helps manage databases, generate TypeScript types from your schema, and run queries in a fully type-safe way. + +We use [PostgreSQL](https://www.postgresql.org) as our default database, but thanks to Drizzle's flexibility, you can easily switch to MySQL, SQLite or any [other supported database](https://orm.drizzle.team/docs/connect-overview) by updating a few configuration lines. + + + } /> + + } /> + diff --git a/.context/turbostarter-framework-context/sections/web/storage/configuration.md b/.context/turbostarter-framework-context/sections/web/storage/configuration.md new file mode 100644 index 0000000..27f0b7b --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/storage/configuration.md @@ -0,0 +1,31 @@ +--- +title: Configuration +description: Learn how to configure storage in TurboStarter. +url: /docs/web/storage/configuration +--- + +# Configuration + +Currently, TurboStarter supports all S3-compatible storage providers, including [AWS S3](https://aws.amazon.com/s3/), [DigitalOcean Spaces](https://www.digitalocean.com/products/spaces), [Cloudflare R2](https://www.cloudflare.com/products/r2/), and [Supabase Storage](https://supabase.com/storage). + +For a concrete example using Supabase Storage as an S3-compatible provider, see the [Supabase recipe](/docs/web/recipes/supabase#use-supabase-storage-as-s3-compatible-storage). + +The setup process is straightforward - you just need to configure a few environment variables in both your local environment and hosting provider: + +```dotenv +S3_REGION= +S3_BUCKET= +S3_ENDPOINT= +S3_ACCESS_KEY_ID= +S3_SECRET_ACCESS_KEY= +``` + +Let's break down each required variable: + +* `S3_REGION`: The [AWS region](https://aws.amazon.com/about-aws/global-infrastructure/regions_az/) where your storage is located - defaults to `us-east-1` +* `S3_BUCKET`: The default name of your storage bucket - you can pass different for each request +* `S3_ENDPOINT`: The S3 [endpoint URL](https://docs.aws.amazon.com/general/latest/gr/s3.html) for your storage provider - defaults to `https://s3.amazonaws.com` +* `S3_ACCESS_KEY_ID`: Your storage provider's [access key ID](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html) +* `S3_SECRET_ACCESS_KEY`: Your storage provider's [secret access key](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_access-keys.html) + +You can learn more about S3 service configuration in the [official AWS documentation](https://docs.aws.amazon.com/AmazonS3/latest/API/Welcome.html) or your specific storage provider's documentation. diff --git a/.context/turbostarter-framework-context/sections/web/storage/managing-files.md b/.context/turbostarter-framework-context/sections/web/storage/managing-files.md new file mode 100644 index 0000000..785cf36 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/storage/managing-files.md @@ -0,0 +1,178 @@ +--- +title: Managing files +description: Learn how to manage files in TurboStarter. +url: /docs/web/storage/managing-files +--- + +# Managing files + +Before you start managing files, make sure you have [configured storage](/docs/web/storage/configuration). + +## Permissions + +Most S3-compatible storage providers allow you to configure bucket permissions and access policies. It's crucial to properly set these up to secure your files and control who can access them. + +Here are some key security recommendations: + +* Keep your bucket private by default +* Use IAM roles and policies to manage access +* Enable server-side encryption for sensitive data +* Configure CORS settings appropriately for client-side uploads +* Regularly audit bucket permissions and access logs + +Making your bucket public is strongly discouraged as it can expose sensitive data and lead to unauthorized access and unexpected costs from bandwidth usage. + +For detailed guidance on configuring bucket policies and permissions, refer to your storage provider's documentation: + +* [AWS S3 Security Documentation](https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-policy-language-overview.html) +* [DigitalOcean Spaces Security](https://docs.digitalocean.com/products/spaces/how-to/manage-access/) +* [Cloudflare R2 Security](https://developers.cloudflare.com/r2/api/s3/tokens/) +* [Supabase Storage Security](https://supabase.com/docs/guides/storage/security/access-control) + +## Uploading files + +As explained in the [overview](/docs/web/storage/overview), TurboStarter uses presigned URLs to upload files to your storage provider. + +We prepared a special endpoint to generate presigned URLs for your uploads to use in your client-side code. + +```ts title="storage/router.ts" +export const storageRouter = new Hono().get( + "/upload", + enforceAuth, + validate("query", getObjectUrlSchema), + async (c) => c.json(await getUploadUrl(c.req.valid("query"))), +); +``` + + + The signed URL is only valid for a limited time and will work for anyone who has access to it during that period. Make sure to handle the URL securely and avoid exposing it to unauthorized users. + + +Then, you can use it to upload files to the generated presigned URL from your frontend code: + +```tsx title="upload.tsx" +const upload = useMutation({ + mutationFn: async (data: { file?: File }) => { + const extension = data.file?.type.split("/").pop(); + const path = `files/${crypto.randomUUID()}.${extension}`; + + const { url: uploadUrl } = await handle(api.storage.upload.$get)({ + query: { path }, + }); + + const response = await fetch(uploadUrl, { + method: "PUT", + body: data.file, + headers: { + "Content-Type": data.file?.type ?? "", + }, + }); + + if (!response.ok) { + throw new Error("Failed to upload file!"); + } + }, + onError: (error) => { + toast.error(error.message}); + }, + onSuccess: async ({ publicUrl, oldImage }, _b, context) => { + toast.success("File uploaded!"); + }, + }); +``` + +The code above demonstrates how to implement file uploads in your application: + +1. First, we have a server-side endpoint (`storageRouter`) that generates presigned URLs for uploads. This endpoint: + * [Requires authentication](/docs/web/api/protected-routes) via `enforceAuth` + * Validates the request parameters using `validate` + * Returns a presigned URL for uploading + +2. Then, in the frontend code (`upload.tsx`), we use React Query's `useMutation` hook to handle the upload process: + * Requests a presigned URL from the server + * Uploads the file directly to the storage provider using the presigned URL + * Handles success and error cases with toast notifications + +This approach ensures secure file uploads while avoiding server bandwidth costs and function timeout issues. + +### Public uploads + +Although **it's not recommended** to use public uploads in production, you can use the same endpoint to generate presigned URLs for public uploads: + +```ts title="storage/router.ts" +export const storageRouter = new Hono().get( + "/upload", + validate("query", getObjectUrlSchema), + async (c) => c.json(await getUploadUrl(c.req.valid("query"))), +); +``` + +Just remove the `enforceAuth` middleware from the endpoint and keep rest of the logic the same. + +## Displaying files + +We provide dedicated endpoints for retrieving signed URLs specifically for displaying files. These URLs are time-limited to maintain security, so they cannot be used for permanent storage or long-term access: + +```ts title="storage/router.ts" +export const storageRouter = new Hono().get( + "/signed", + enforceAuth, + validate("query", getObjectUrlSchema), + async (c) => c.json(await getSignedUrl(c.req.valid("query"))), +); +``` + +This endpoint is perfect for displaying files that should only be accessible to authorized users for a limited time. + +### Public files + +For displaying files publicly (without authorization and time limitations), you can use the `/public` endpoint: + +```ts title="storage/router.ts" +export const storageRouter = new Hono().get( + "/public", + validate("query", getObjectUrlSchema), + async (c) => c.json(await getPublicUrl(c.req.valid("query"))), +); +``` + +This endpoint generates a public URL for the file that you can use to display in your application. Please ensure that your bucket policy allows public access to the files and verify that you're not exposing any sensitive information. + +## Deleting files + +Deleting files works almost the same way as uploading files. You just need to generate a presigned URL for deletion and then use it to remove the file: + +```ts title="storage/router.ts" +export const storageRouter = new Hono().get( + "/delete", + validate("query", getObjectUrlSchema), + async (c) => c.json(await getDeleteUrl(c.req.valid("query"))), +); +``` + +Then, in the frontend code, we use React Query's `useMutation` hook to handle the deletion process: + +```tsx title="delete.tsx" +const remove = useMutation({ + mutationFn: async () => { + const path = file.split("/").pop(); + if (!path) return; + + const { url: deleteUrl } = await handle(api.storage.delete.$get)({ + query: { path: `files/${path}` }, + }); + + await fetch(deleteUrl, { + method: "DELETE", + }); + }, + onError: (error) => { + toast.error(error.message); + }, + onSuccess: () => { + toast.success("File removed!"); + }, +}); +``` + +Now that you understand how to manage files in TurboStarter, it's time to build something awesome! Try creating a file upload component, building a photo gallery, or implementing a document management system. diff --git a/.context/turbostarter-framework-context/sections/web/storage/overview.md b/.context/turbostarter-framework-context/sections/web/storage/overview.md new file mode 100644 index 0000000..e9db319 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/storage/overview.md @@ -0,0 +1,34 @@ +--- +title: Overview +description: Get started with storage in TurboStarter. +url: /docs/web/storage/overview +--- + +# Overview + +With TurboStarter, you can easily upload and manage files (images, videos, documents, and more) in your application. + +Currently, all S3-compatible storage providers are supported, including [AWS S3](https://aws.amazon.com/s3/), [DigitalOcean Spaces](https://www.digitalocean.com/products/spaces), [Cloudflare R2](https://www.cloudflare.com/products/r2/), [Supabase Storage](https://supabase.com/storage), and others. + +If you're using Supabase, you can follow the [Supabase recipe](/docs/web/recipes/supabase#use-supabase-storage-as-s3-compatible-storage) for a concrete example of configuring Supabase Storage as your S3-compatible backend. + +## Uploading files + +The most common approach to uploading files is to use client-side uploads. With client-side uploads, you avoid paying ingress/egress fees for transferring file binary data through your server. + +Additionally, most hosting platforms like [Vercel](https://vercel.com/docs/functions/runtimes#size-limits) or [Netlify](https://answers.netlify.com/t/what-is-the-maximum-file-size-upload-limit-in-a-netlify-form-submission/108419) have limitations on file size and maximum serverless function execution time. + +That's why TurboStarter utilizes the **presigned URLs** feature of storage providers to upload files. Instead of sending files to the serverless function, the client requests a time-limited presigned URL from the serverless function and then uploads the file directly to the storage provider. + + + +1. Client **requests** a presigned URL from the serverless function. +2. Server parses the request, validates the payload, optionally saves the metadata, and **returns the presigned URL** to the client. +3. Client **uploads the file** to the presigned URL within the expiration time. +4. (Optional) Once the file is uploaded, the serverless function is notified about the upload event, and the file metadata is saved to the database. + + + This approach ensures that credentials remain secure, handles authorization and authentication properly, and avoids the limitations of serverless platforms. + + +The configuration and use of storage is straightforward and simple. We'll explore this in more detail in the following sections. diff --git a/.context/turbostarter-framework-context/sections/web/tests/e2e.md b/.context/turbostarter-framework-context/sections/web/tests/e2e.md new file mode 100644 index 0000000..bb2c6e9 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/tests/e2e.md @@ -0,0 +1,15 @@ +--- +title: E2E tests +description: Simulate real user scenarios across the entire stack with automated end-to-end test tools and examples. +url: /docs/web/tests/e2e +--- + +# E2E tests + + + End-to-end (E2E) tests will be available soon, allowing you to automate testing of real user flows and interactions across your application. + + Stay tuned for updates as we roll out robust E2E testing resources and examples. + + [See roadmap](https://github.com/orgs/turbostarter/projects/1) + diff --git a/.context/turbostarter-framework-context/sections/web/tests/unit.md b/.context/turbostarter-framework-context/sections/web/tests/unit.md new file mode 100644 index 0000000..88599f1 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/tests/unit.md @@ -0,0 +1,136 @@ +--- +title: Unit tests +description: Write and run fast unit tests for individual functions and components with instant feedback. +url: /docs/web/tests/unit +--- + +# Unit tests + +Unit tests are a type of automated test where individual units or components are tested. The "unit" in "unit test" refers to the smallest testable parts of an application. These tests are designed to verify that each unit of code performs as expected. + +TurboStarter uses [Vitest](https://vitest.dev) as the unit testing framework. It's a blazing-fast test runner built on top of [Vite](https://vitejs.dev), designed for modern JavaScript and TypeScript projects. + + + If you've used [Jest](https://jestjs.io) before, you already know Vitest - it shares the same API. But Vitest is built for speed: native TypeScript support without transpilation, parallel test execution, and a smart watch mode that only re-runs tests affected by your changes. + + It comes with everything you need out of the box - code coverage, snapshot testing, mocking, and a slick UI for debugging. Fast feedback, zero configuration. + + +## Why write unit tests? + +Unit tests give you **fast, focused feedback** on small pieces of your code - individual functions, hooks, or components. Instead of debugging an entire page or flow, you can verify just the logic you care about in isolation. + +They also act as **living documentation**: a good test tells you how a function is supposed to behave, which edge cases are important, and what assumptions the code makes. This makes it much easier to safely refactor or extend features later. + +In TurboStarter, unit tests are designed to be **cheap and quick to run**, so you can keep Vitest running in watch mode while you code. Every change you make is immediately checked, helping you catch regressions before they ever reach integration or end‑to‑end tests. + +## Configuration + +TurboStarter configures Vitest to be **as simple as possible**, while still taking advantage of [Turborepo's caching](https://turborepo.com/docs/crafting-your-repository/caching) and Vitest's [Test Projects](https://vitest.dev/guide/projects). + +```ts title="vitest.config.ts" +import { mergeConfig } from "vitest/config"; + +import baseConfig from "@turbostarter/vitest-config/base"; + +export default mergeConfig(baseConfig, { + test: { + /* your extended test configuration here */ + }, +}); +``` + +* **Per-package tests**: each package that has unit tests defines its own `test` script. This keeps the configuration close to the code and makes it easy to add tests to any workspace. +* **Turbo tasks for CI**: the root `test` task (`pnpm test`) uses `turbo run test` to execute all package-level test scripts with smart caching, which is ideal for CI pipelines where you want to avoid re-running unchanged tests. +* **Vitest Test Projects for local dev**: a root Vitest configuration uses [Test Projects](https://vitest.dev/guide/projects) to run all unit test suites from a single command, which is perfect for local development when you want fast feedback across the whole monorepo. + +This **hybrid setup** combines Turborepo and Vitest Projects in a way that fits TurboStarter's principles: cached, package-aware runs in CI, and a single, unified Vitest entry point for local development. + +You can read more about this setup in the official documentation guides listed below. + + + + + + + +## Running tests + +There are a few different ways to run unit tests, depending on what you're doing: + +* **CI / full test run** - at the root of the repo: + +```bash +pnpm test +``` + +This runs `turbo run test`, which executes all `test` scripts in packages that define them, with Turborepo handling caching so unchanged packages are skipped. This is what you should use in your CI/CD pipeline. + +* **One-off local run with Vitest Projects**: + +```bash +pnpm test:projects +``` + +This uses Vitest [Test Projects](https://vitest.dev/guide/projects) to run all configured unit test suites from a single command, which is great when you want to quickly validate the whole monorepo locally. + +* **Watch mode during development**: + +```bash +pnpm test:projects:watch +``` + +This starts Vitest in watch mode across all Test Projects. As you edit files, only the affected tests are re-run, giving you fast feedback while you work. + +## Code coverage + +Unit test coverage helps you understand **how much** of your code is being tested. While it can't guarantee bug-free code, it shines a light on untested paths that could hide issues or regressions. + +To generate a code coverage report for all unit tests, run: + +```bash +pnpm turbo test:coverage +``` + +This command runs the coverage task across all relevant packages (using Turborepo) and collects the results into a single coverage output. + +To open the coverage report in your browser: + +```bash +pnpm turbo test:coverage:view +``` + +This will build the HTML report and launch it using your default browser, so you can explore which files and branches are covered. + + + You can also store the generated coverage report as a [GitHub Actions artifact](https://docs.github.com/en/actions/using-workflows/storing-workflow-data-as-artifacts) during your CI/CD pipeline, just add the following steps to your workflow job: + + ```yaml title=".github/workflows/ci.yml" + # your workflow job configuration here + + - name: 📊 Generate coverage + run: pnpm turbo test:coverage + + - name: 🗃️ Archive coverage report + uses: actions/upload-artifact@v5 + with: + name: coverage-${{ github.sha }} + path: tooling/vitest/coverage/report + ``` + + This will generate a test coverage report and upload it as an artifact, so you can access it from GitHub Actions tab for later inspection. + + +A high coverage percentage means your tests execute most lines and branches - but the quality and relevance of your tests matter more than the raw number. Use coverage reports to spot gaps and guide improvements, not as the sole metric of test health. + +![Code coverage](/images/docs/code-coverage.png) + +## Best practices + +Unit tests should work **for you**, not the other way around. Focus on writing tests that make it easier to change code with confidence, not on satisfying arbitrary rules or reaching a magic number in a dashboard. + +Code coverage is a **useful metric**, but it **SHOULD NOT** be the goal. It's better to have a smaller set of high‑value tests that cover critical paths and edge cases than a huge suite of fragile tests that are hard to maintain. + +When in doubt, ask: *“Does this test give **me** confidence that I can change this code without breaking users?”* If the answer is no, refactor or remove it. + +Finally, keep unit tests focused on **small, isolated pieces of logic**. More advanced flows — like multi-step user journeys, cross-service interactions, or full-page behavior — are better covered by [end-to-end (E2E) tests](/docs/web/tests/e2e), where you can verify the system as a whole. diff --git a/.context/turbostarter-framework-context/sections/web/troubleshooting/billing.md b/.context/turbostarter-framework-context/sections/web/troubleshooting/billing.md new file mode 100644 index 0000000..1ba553e --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/troubleshooting/billing.md @@ -0,0 +1,25 @@ +--- +title: Billing +description: Find answers to common billing issues. +url: /docs/web/troubleshooting/billing +--- + +# Billing + +## Checkout can't be created + +This happen in the following cases: + +1. The environment variables are not set correctly. Please make sure you have set the environment variables corresponding to your billing provider in `.env.local` if locally - or in your hosting provider's dashboard if in production +2. The price IDs used are incorrect. Make sure to use the exact price IDs as they are in the payment provider's dashboard. + +[Read more about billing configuration](/docs/web/billing/configuration) + +## Database is not updated after subscribing to a plan + +This may happen if the webhook is not set up correctly. Please make sure you have set up the webhook in the payment provider's dashboard and that the URL is correct. + +If working locally, make sure that: + +1. If using Stripe, that the Stripe CLI or configured proxy is up and running ([see the Stripe documentation for more information](/docs/web/billing/stripe#create-a-webhook)) +2. If using Lemon Squeezy, that the webhook set in Lemon Squeezy is correct and that the server is running. Additionally, make sure the proxy is set up correctly if you are testing locally ([see the Lemon Squeezy documentation for more information](/docs/web/billing/lemon-squeezy#create-a-webhook)). diff --git a/.context/turbostarter-framework-context/sections/web/troubleshooting/deployment.md b/.context/turbostarter-framework-context/sections/web/troubleshooting/deployment.md new file mode 100644 index 0000000..c809ee9 --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/troubleshooting/deployment.md @@ -0,0 +1,45 @@ +--- +title: Deployment +description: Find answers to common web deployment issues. +url: /docs/web/troubleshooting/deployment +--- + +# Deployment + +## Deployment build fails + +This is most likely an issue related to the environment variables not being set correctly in the deployment environment. Please analyse the logs of the deployment provider to see what is the issue. + +The kit is very defensive about incorrect environment variables, and will throw an error if any of the required environment variables are not set. In this way - the build will fail if the environment variables are not set correctly - instead of deploying a broken application. + +Check our guides for the most popular hosting providers for more information on how to deploy your TurboStarter project correctly: + + + + + + + + + + + + + + + + + +## What should I set as a URL before my first deployment? + +That's very good question! For the first deployment you can set any URL, and then, after you (or your provider) assign a domain name, you can change it to the correct one. There's nothing wrong with redeploying your project multiple times. + +## Sign in with OAuth provider doesn't work + +This is most likely a settings issues in the provider's settings. To troubleshoot this issue, follow these steps: + +1. **Verify provider settings**: Ensure that the OAuth provider's settings are correctly configured. Check that the client ID, client secret, and redirect URI are accurate and match the values in your application. +2. **Check environment variables**: Confirm that the environment variables for the OAuth provider are set correctly in your application production environment. +3. **Validate callback URLs**: Ensure that the callback URLs for each provider are set correctly and match the URLs in your application. This is crucial for the OAuth flow to work correctly. + +Please read [Better Auth documentation](https://www.better-auth.com/docs/concepts/oauth) for more information on how to set up third-party providers. diff --git a/.context/turbostarter-framework-context/sections/web/troubleshooting/emails.md b/.context/turbostarter-framework-context/sections/web/troubleshooting/emails.md new file mode 100644 index 0000000..a9b318a --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/troubleshooting/emails.md @@ -0,0 +1,44 @@ +--- +title: Emails +description: Find answers to common emails issues. +url: /docs/web/troubleshooting/emails +--- + +# Emails + +## I want to use a different email provider + +Of course! You can use any email provider that you want. All you need to do is to implement the `EmailProviderStrategy` and export it in your `index.ts` file. + +[Read more about sending emails](/docs/web/emails/sending) + +## My emails are landing in the spam folder + +Emails landing in spam folders is a common issue. Here are key steps to improve deliverability: + +1. **Configure proper domain setup**: + * Use a dedicated subdomain for sending emails (e.g., mail.yourdomain.com) + * Ensure [reverse DNS (PTR) records](https://www.cloudflare.com/learning/dns/dns-records/dns-ptr-record/) are properly configured + * Warm up your sending domain gradually + +2. **Implement authentication protocols**: + * Set up [SPF records](https://www.cloudflare.com/learning/dns/dns-records/dns-spf-record/) to specify authorized sending servers + * Enable [DKIM signing](https://www.cloudflare.com/learning/dns/dns-records/dns-dkim-record/) to verify email authenticity + * Configure [DMARC policies](https://www.cloudflare.com/learning/dns/dns-records/dns-dmarc-record/) to prevent spoofing + +3. **Follow deliverability best practices**: + * Include clear unsubscribe mechanisms in all marketing communications + * Personalize content appropriately + * Avoid excessive promotional language and spam triggers + * Maintain consistent HTML formatting and styling + * Only include links to verified domains + * Keep a regular sending schedule + * Clean your email lists regularly + * Use double opt-in for new subscribers + +4. **Monitor and optimize**: + * Track key metrics like delivery rates, opens, and bounces + * Monitor spam complaint rates + * Review email authentication reports + * Test emails across different clients and devices + * Adjust sending practices based on performance data diff --git a/.context/turbostarter-framework-context/sections/web/troubleshooting/installation.md b/.context/turbostarter-framework-context/sections/web/troubleshooting/installation.md new file mode 100644 index 0000000..fbfa3ab --- /dev/null +++ b/.context/turbostarter-framework-context/sections/web/troubleshooting/installation.md @@ -0,0 +1,99 @@ +--- +title: Installation +description: Find answers to common web installation issues. +url: /docs/web/troubleshooting/installation +--- + +# Installation + +## Cannot clone the repository + +Issues related to cloning the repository are usually related to a Git misconfiguration in your local machine. The commands displayed in this guide using SSH: these will work only if you have setup your SSH keys in Github. + +If you run into issues, [please make sure you follow this guide to set up your SSH key in Github.](https://docs.github.com/en/authentication/connecting-to-github-with-ssh) + +If this also fails, please use HTTPS instead. You will be able to see the commands in the repository's Github page under the "Clone" dropdown. + +Please also make sure that the account that accepted the invite to TurboStarter, and the locally connected account are the same. + +## My environment variables from `.env.local` file are not being loaded + +Make sure you are running the `pnpm dev` command from the root directory of your project (where the `pnpm-workspace.yaml` file is located) + +Also, ensure that the `.env.local` files are present in the apps that need them. For example, the `.env` file should be present in the `apps/web` directory for the web app. + + + TurboStarter uses the `dotenv-cli` to load environment variables from a `.env` files. The `dotenv-cli` is automatically used when running the `pnpm dev` command from the root directory. + + +## Next.js server doesn't start + +This may happen due to some issues in the packages. Try to clean the workspace using the following command: + +```bash +pnpm clean +``` + +Then, reinstall the dependencies: + +```bash +pnpm i +``` + +You can now retry running the dev server. + +## Local database doesn't start + +If you cannot run the local database container, it's likely you have not started [Docker](https://docs.docker.com/get-docker/) locally. Our local database requires Docker to be installed and running. + +Please make sure you have installed Docker (or compatible software such as [Colima](https://github.com/abiosoft/colima), [Orbstack](https://github.com/orbstack/orbstack)) and that is running on your local machine. + +Also, make sure that you have enough [memory and CPU allocated](https://docs.docker.com/engine/containers/resource_constraints/) to your Docker instance. + +## I don't see my translations + +If you don't see your translations appearing in the application, there are a few common causes: + +1. Check that your translation `.json` files are properly formatted and located in the correct directory +2. Verify that the language codes in your configuration match your translation files +3. Enable debug mode (`debug: true`) in your i18next configuration to see detailed logs + +[Read more about configuration for translations](/docs/web/internationalization/configuration) + +## "Module not found" error + +This issue is mostly related to either dependency installed in the wrong package or issues with the file system. + +The most common cause is incorrect dependency installation. Here's how to fix it: + +1. Clean the workspace: + + ```bash + pnpm clean + ``` + +2. Reinstall the dependencies: + ```bash + pnpm i + ``` + +If you're adding new dependencies, make sure to install them in the correct package: + +```bash +# For main app dependencies +pnpm install --filter web my-package + +# For a specific package +pnpm install --filter @turbostarter/ui my-package +``` + +If the issue persists, please check the file system for any issues. + +### Windows OneDrive + +OneDrive can cause file system issues with Node.js projects due to its file syncing behavior. If you're using Windows with OneDrive, you have two options to resolve this: + +1. Move your project to a location outside of OneDrive-synced folders (recommended) +2. Disable OneDrive sync specifically for your development folder + +This prevents file watching and symlink issues that can occur when OneDrive tries to sync Node.js project files. diff --git a/.context/turbostarter-framework-context/wireframes/ARCHITECTURE.md b/.context/turbostarter-framework-context/wireframes/ARCHITECTURE.md new file mode 100644 index 0000000..dc90191 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/ARCHITECTURE.md @@ -0,0 +1,290 @@ +# TurboStarter Wireframe Library Architecture + +**Date:** 2026-02-01 16:15 +**Context:** Designing a reusable Excalidraw wireframe library based on TurboStarter boilerplate UI + +## Overview + +A comprehensive wireframe system for TurboStarter projects featuring: +- Token-based theming (18 color variants) +- Component templates (reusable building blocks) +- Progressive fidelity (LOW → MEDIUM → HIGH) +- Maximum parallelization (3-5 agents per wave) + +## Architecture Diagram + +``` +┌─────────────────────────────────────────────────────────────┐ +│ TURBOSTARTER WIREFRAME LIBRARY │ +├─────────────────────────────────────────────────────────────┤ +│ │ +│ 1. TOKEN-BASED THEMING │ +│ └─ $tokens → apply-theme.js → 18 color variants │ +│ │ +│ 2. COMPONENT TEMPLATES (Wave 0) │ +│ └─ Reusable layouts + components = building blocks │ +│ │ +│ 3. PROGRESSIVE FIDELITY (Waves 1-3) │ +│ └─ LOW → copy+enhance → MEDIUM → copy+enhance → HIGH │ +│ │ +│ 4. MAX PARALLELIZATION │ +│ └─ 3-5 agents per wave, templates inherited │ +│ │ +└─────────────────────────────────────────────────────────────┘ +``` + +## Folder Structure + +``` +_bmad-output/excalidraw-diagrams/ +├── CLAUDE.md # AI context for managing wireframes +├── wireframe-themes.json # 18 theme color definitions +├── apply-theme.js # Script to apply themes +├── wireframe-theming.md # Detailed theming docs +│ +├── _templates/ # SHARED BASE COMPONENTS +│ ├── layouts/ +│ │ ├── dashboard.excalidraw # Sidebar + Header + Content +│ │ ├── auth.excalidraw # Two-column auth split +│ │ └── marketing.excalidraw # Header + Content + Footer +│ └── components/ +│ ├── sidebar.excalidraw # Collapsible sidebar +│ ├── header.excalidraw # Dashboard header pattern +│ ├── data-table.excalidraw # Table + toolbar + pagination +│ ├── card-grid.excalidraw # 3-column card layout +│ ├── form.excalidraw # Form with inputs/buttons +│ └── modal.excalidraw # Dialog/modal pattern +│ +├── low-fidelity/ # Quick sketches +├── medium-fidelity/ # Defined elements +└── high-fidelity/ # Detailed wireframes +``` + +## Theming System + +### Token Colors + +| Token | Purpose | +|-------|---------| +| `$background` | Page/screen background | +| `$foreground` | Primary text | +| `$primary` | Brand color, CTAs | +| `$secondary` | Secondary backgrounds | +| `$muted` | Disabled/placeholder | +| `$border` | Borders, dividers | +| `$card` | Card backgrounds | +| `$destructive` | Delete/error | +| `$success` | Success states | +| `$sidebar` | Sidebar background | +| `$sidebar-foreground` | Sidebar text | + +### Available Themes (9 colors × 2 modes = 18) + +- orange-light, orange-dark +- blue-light, blue-dark +- green-light, green-dark +- red-light, red-dark +- rose-light, rose-dark +- violet-light, violet-dark +- yellow-light, yellow-dark +- gray-light, gray-dark +- stone-light, stone-dark + +### Apply Theme Command + +```bash +node apply-theme.js [output.excalidraw] +``` + +## Screen Inventory + +### Auth Screens (4) +| Screen | Template Base | Description | +|--------|---------------|-------------| +| auth-login | layout-auth | Email/password + OAuth | +| auth-register | layout-auth | Registration form | +| auth-forgot-password | layout-auth | Password reset | +| auth-join-org | layout-auth | Organization invitation | + +### Dashboard Layouts (3) +| Screen | Template Base | Description | +|--------|---------------|-------------| +| dashboard-user | layout-dashboard | User dashboard home | +| dashboard-org | layout-dashboard | Organization analytics | +| dashboard-admin | layout-dashboard | Admin panel | + +### Sidebars (3) +| Screen | Template Base | Description | +|--------|---------------|-------------| +| sidebar-apps | component-sidebar | Apps navigation | +| sidebar-dashboard | component-sidebar | User dashboard nav | +| sidebar-admin | component-sidebar | Admin navigation | + +### Settings Pages (3) +| Screen | Template Base | Description | +|--------|---------------|-------------| +| settings-general | layout-dashboard + form | Profile settings | +| settings-security | layout-dashboard + form | 2FA, passkeys | +| settings-billing | layout-dashboard + card-grid | Plans, credits | + +### Data Components (3) +| Screen | Template Base | Description | +|--------|---------------|-------------| +| data-table-users | component-data-table | Admin users table | +| data-table-members | component-data-table | Org members | +| data-table-toolbar | component-data-table | Filters, search | + +## Fidelity Levels + +### LOW Fidelity +- Basic rectangles and shapes +- Placeholder text ("xxxxx") +- No styling details +- Focus on layout and flow +- **Use for:** Early concepts, quick iteration + +### MEDIUM Fidelity +- Defined UI elements (buttons, inputs) +- Representative labels +- Basic iconography (rectangles with X) +- Approximate sizing +- **Use for:** Design reviews, stakeholder feedback + +### HIGH Fidelity +- Realistic element sizes +- Actual content examples +- Proper spacing and alignment +- Icon placeholders that match intent +- **Use for:** Developer handoff, final approval + +## Execution Plan + +### Wave 0: Templates (3 parallel agents) + +| Agent | Output | +|-------|--------| +| T1 | layouts/dashboard, layouts/auth, layouts/marketing | +| T2 | components/sidebar, components/header, components/data-table | +| T3 | components/card-grid, components/form, components/modal | + +**Output:** 9 template files + +### Wave 1: LOW Fidelity (5 parallel agents) + +| Agent | Screens | Uses Templates | +|-------|---------|----------------| +| A | auth-login, auth-register, auth-forgot-password, auth-join-org | layout-auth | +| B | dashboard-user, dashboard-org, dashboard-admin | layout-dashboard | +| C | sidebar-apps, sidebar-dashboard, sidebar-admin | component-sidebar | +| D | settings-general, settings-security, settings-billing | layout-dashboard + form | +| E | data-table-users, data-table-members, data-table-toolbar | component-data-table | + +**Output:** 16 LOW fidelity files + +### Wave 2: MEDIUM Fidelity (5 parallel agents) + +Same agent assignments. Each agent: +1. Reads corresponding LOW file +2. Copies all elements +3. Enhances with labels, better proportions, sizing +4. Saves to medium-fidelity/ + +**Output:** 16 MEDIUM fidelity files + +### Wave 3: HIGH Fidelity (5 parallel agents) + +Same agent assignments. Each agent: +1. Reads corresponding MEDIUM file +2. Copies all elements +3. Adds detail, realistic content, final polish +4. Saves to high-fidelity/ + +**Output:** 16 HIGH fidelity files + +## Total Output + +| Category | Files | +|----------|-------| +| Templates | 9 | +| LOW fidelity | 16 | +| MEDIUM fidelity | 16 | +| HIGH fidelity | 16 | +| **Total** | **57 files** | + +With 18 theme variants available = unlimited themed outputs + +## TurboStarter UI Reference + +### Standard Dimensions (Desktop 1440×900) +- Sidebar width: 280px (expanded), 60px (collapsed) +- Header height: 64px +- Content padding: 24px +- Card gap: 16px +- Button height: 40px +- Input height: 40px + +### Layout Patterns + +**Dashboard Layout:** +``` +┌─────────────────────────────────────────┐ +│ SidebarProvider │ +│ ┌──────────┬──────────────────────────┐ │ +│ │ Sidebar │ SidebarInset │ │ +│ │ ┌──────┐ │ ┌──────────────────────┐ │ │ +│ │ │Header│ │ │ DashboardHeader │ │ │ +│ │ ├──────┤ │ ├──────────────────────┤ │ │ +│ │ │Content│ │ │ Page Content │ │ │ +│ │ ├──────┤ │ │ │ │ │ +│ │ │Footer│ │ │ │ │ │ +│ │ └──────┘ │ └──────────────────────┘ │ │ +│ └──────────┴──────────────────────────┘ │ +└─────────────────────────────────────────┘ +``` + +**Auth Layout:** +``` +┌────────────────────┬────────────────────┐ +│ │ │ +│ Logo + Form │ Branding/Image │ +│ (Auth content) │ (Decorative) │ +│ │ │ +└────────────────────┴────────────────────┘ +``` + +## Agent Instructions Template + +### For Template Creation (Wave 0) +``` +Create Excalidraw template at [path]. +Use $tokens for all colors (see wireframe-themes.json). +Follow grid alignment (20px). +Include: [specific elements for this template] +Standard desktop: 1440×900. +``` + +### For Screen Creation (Waves 1-3) +``` +[LOW] Create screen by copying [template], customize for [screen purpose]. +[MEDIUM] Read [low file], copy elements, enhance with labels and sizing. +[HIGH] Read [medium file], copy elements, add detail and realistic content. +Save to [fidelity-level]/[screen-name].excalidraw. +``` + +## Related Files + +- **Theme source:** `packages/ui/shared/src/styles/themes/` +- **UI components:** `packages/ui/` +- **App pages:** `apps/web/src/app/[locale]/` +- **Feature modules:** `apps/web/src/modules/` +- **Wireframe docs:** `_bmad-output/excalidraw-diagrams/CLAUDE.md` + +## Next Steps + +1. Execute Wave 0 (templates) - 3 parallel agents +2. Review templates +3. Execute Wave 1 (LOW) - 5 parallel agents +4. Review LOW screens +5. Execute Wave 2 (MEDIUM) - 5 parallel agents +6. Execute Wave 3 (HIGH) - 5 parallel agents +7. Final review and validation diff --git a/.context/turbostarter-framework-context/wireframes/CLAUDE.md b/.context/turbostarter-framework-context/wireframes/CLAUDE.md new file mode 100644 index 0000000..1e540b9 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/CLAUDE.md @@ -0,0 +1,154 @@ +# TurboStarter Wireframe Library + +## Overview + +This folder contains reusable Excalidraw wireframes based on TurboStarter's boilerplate UI. Use these as templates when creating project-specific wireframes. + +**Full Architecture:** See `ARCHITECTURE.md` for complete details on the design system, execution plan, and screen inventory. + +## Folder Structure + +``` +.context/turbostarter-framework-context/wireframes/ +├── CLAUDE.md # This file - AI context +├── ARCHITECTURE.md # Full architecture documentation +├── wireframe-themes.json # Theme color definitions (8 variants) +├── apply-theme.js # Script to apply themes to wireframes +├── wireframe-theming.md # Detailed theming documentation +│ +├── _templates/ # Reusable building blocks ($tokens) +│ ├── layouts/ # dashboard, auth, marketing +│ └── components/ # sidebar, header, data-table, etc. +│ +├── low-fidelity/ # Basic layouts ($tokens) +├── medium-fidelity/ # + Labels & content ($tokens) +├── high-fidelity/ # + Final polish ($tokens) +│ +└── themed/ # Ready-to-view files (actual colors) + ├── high/ # HIGH fidelity with orange theme + ├── medium/ # MEDIUM fidelity with orange theme + ├── low/ # LOW fidelity with orange theme + └── templates/ # Templates with orange theme +``` + +## Quick Start + +### View Wireframes (Open in Excalidraw) + +Use files from `themed/` folder - they have actual colors: +``` +themed/high/auth-login.excalidraw +themed/high/dashboard-user.excalidraw +themed/high/data-table-users.excalidraw +``` + +### Change Theme + +```bash +# Apply different theme to all files +cd .context/turbostarter-framework-context/wireframes +for f in high-fidelity/*.excalidraw; do + node apply-theme.js "$f" blue-light "themed/high/$(basename $f)" +done +``` + +Available themes: `orange-light`, `orange-dark`, `blue-light`, `blue-dark`, `green-light`, `green-dark`, `violet-light`, `violet-dark` + +## Token Colors (For Creating New Wireframes) + +| Token | Purpose | +|-------|---------| +| `$background` | Page/screen background | +| `$foreground` | Primary text | +| `$primary` | Brand color, CTAs | +| `$primary-foreground` | Text on primary | +| `$secondary` | Secondary backgrounds | +| `$muted` | Disabled/placeholder backgrounds | +| `$muted-foreground` | Disabled/placeholder text | +| `$border` | Borders, dividers | +| `$card` | Card backgrounds | +| `$destructive` | Delete/error | +| `$success` | Success states | +| `$sidebar` | Sidebar background | +| `$sidebar-foreground` | Sidebar text | + +## Screen Inventory (16 screens × 3 fidelities = 48 files) + +### Auth Screens (4) +- `auth-login` - Email/password + OAuth +- `auth-register` - Registration form +- `auth-forgot-password` - Password reset +- `auth-join-org` - Organization invitation + +### Dashboard Layouts (3) +- `dashboard-user` - User dashboard with cards +- `dashboard-org` - Organization analytics +- `dashboard-admin` - Admin panel + +### Sidebars (3) +- `sidebar-apps` - Apps navigation +- `sidebar-dashboard` - User dashboard nav +- `sidebar-admin` - Admin navigation + +### Settings (3) +- `settings-general` - Profile, language +- `settings-security` - 2FA, passkeys, sessions +- `settings-billing` - Plans, credits, history + +### Data Tables (3) +- `data-table-users` - Admin users management +- `data-table-members` - Organization members +- `data-table-invitations` - Pending invitations + +## Templates (9 reusable components) + +### Layouts +- `dashboard` - Sidebar + Header + Content +- `auth` - Two-column auth split +- `marketing` - Header + Content + Footer + +### Components +- `sidebar` - Collapsible navigation +- `header` - Dashboard header +- `data-table` - Table + toolbar + pagination +- `card-grid` - 3-column card layout +- `form` - Inputs + buttons + OAuth +- `modal` - Dialog with backdrop + +## Fidelity Levels + +| Level | What | Use For | +|-------|------|---------| +| **LOW** | Boxes, layout only | Early concepts, IA validation | +| **MEDIUM** | + Labels, content | Design reviews, feedback | +| **HIGH** | + Details, polish | Developer handoff, approval | + +## Standard Dimensions + +- Canvas: 1440×900 (desktop) +- Sidebar: 280px wide +- Header: 64px height +- Content padding: 24px +- Card gap: 16px +- Button/Input height: 40-44px +- Grid: 20px + +## Commands + +```bash +# Apply theme +node apply-theme.js input.excalidraw orange-light output.excalidraw + +# Validate JSON +node -e "JSON.parse(require('fs').readFileSync('file.excalidraw')); console.log('Valid')" + +# Open themed folder +open themed/high/ +``` + +## Related Files + +- **Architecture**: `ARCHITECTURE.md` +- **TurboStarter themes**: `packages/ui/shared/src/styles/themes/` +- **UI components**: `packages/ui/` +- **App pages**: `apps/web/src/app/[locale]/` diff --git a/.context/turbostarter-framework-context/wireframes/_templates/components/card-grid.excalidraw b/.context/turbostarter-framework-context/wireframes/_templates/components/card-grid.excalidraw new file mode 100644 index 0000000..6e69902 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/_templates/components/card-grid.excalidraw @@ -0,0 +1,611 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "card-1", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 352, + "height": 180, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["card-group-1"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-1-icon", + "type": "ellipse", + "x": 20, + "y": 20, + "width": 40, + "height": 40, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["card-group-1"], + "roundness": { "type": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-1-title", + "type": "rectangle", + "x": 20, + "y": 72, + "width": 200, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["card-group-1"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-1-desc", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 300, + "height": 32, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["card-group-1"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-1-action", + "type": "rectangle", + "x": 20, + "y": 144, + "width": 100, + "height": 24, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["card-group-1"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-2", + "type": "rectangle", + "x": 368, + "y": 0, + "width": 352, + "height": 180, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["card-group-2"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-2-icon", + "type": "ellipse", + "x": 388, + "y": 20, + "width": 40, + "height": 40, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["card-group-2"], + "roundness": { "type": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-2-title", + "type": "rectangle", + "x": 388, + "y": 72, + "width": 200, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["card-group-2"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-2-desc", + "type": "rectangle", + "x": 388, + "y": 100, + "width": 300, + "height": 32, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["card-group-2"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-2-action", + "type": "rectangle", + "x": 388, + "y": 144, + "width": 100, + "height": 24, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["card-group-2"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-3", + "type": "rectangle", + "x": 736, + "y": 0, + "width": 352, + "height": 180, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["card-group-3"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-3-icon", + "type": "ellipse", + "x": 756, + "y": 20, + "width": 40, + "height": 40, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["card-group-3"], + "roundness": { "type": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-3-title", + "type": "rectangle", + "x": 756, + "y": 72, + "width": 200, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["card-group-3"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-3-desc", + "type": "rectangle", + "x": 756, + "y": 100, + "width": 300, + "height": 32, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["card-group-3"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-3-action", + "type": "rectangle", + "x": 756, + "y": 144, + "width": 100, + "height": 24, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["card-group-3"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-4", + "type": "rectangle", + "x": 0, + "y": 196, + "width": 352, + "height": 180, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["card-group-4"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-4-icon", + "type": "ellipse", + "x": 20, + "y": 216, + "width": 40, + "height": 40, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["card-group-4"], + "roundness": { "type": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-4-title", + "type": "rectangle", + "x": 20, + "y": 268, + "width": 200, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["card-group-4"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-4-desc", + "type": "rectangle", + "x": 20, + "y": 296, + "width": 300, + "height": 32, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["card-group-4"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-4-action", + "type": "rectangle", + "x": 20, + "y": 340, + "width": 100, + "height": 24, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["card-group-4"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-5", + "type": "rectangle", + "x": 368, + "y": 196, + "width": 352, + "height": 180, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["card-group-5"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-5-icon", + "type": "ellipse", + "x": 388, + "y": 216, + "width": 40, + "height": 40, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["card-group-5"], + "roundness": { "type": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-5-title", + "type": "rectangle", + "x": 388, + "y": 268, + "width": 200, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["card-group-5"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-5-desc", + "type": "rectangle", + "x": 388, + "y": 296, + "width": 300, + "height": 32, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["card-group-5"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-5-action", + "type": "rectangle", + "x": 388, + "y": 340, + "width": 100, + "height": 24, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["card-group-5"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-6", + "type": "rectangle", + "x": 736, + "y": 196, + "width": 352, + "height": 180, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["card-group-6"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-6-icon", + "type": "ellipse", + "x": 756, + "y": 216, + "width": 40, + "height": 40, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["card-group-6"], + "roundness": { "type": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-6-title", + "type": "rectangle", + "x": 756, + "y": 268, + "width": 200, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["card-group-6"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-6-desc", + "type": "rectangle", + "x": 756, + "y": 296, + "width": 300, + "height": 32, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["card-group-6"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-6-action", + "type": "rectangle", + "x": 756, + "y": 340, + "width": 100, + "height": 24, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["card-group-6"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/_templates/components/data-table.excalidraw b/.context/turbostarter-framework-context/wireframes/_templates/components/data-table.excalidraw new file mode 100644 index 0000000..3713162 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/_templates/components/data-table.excalidraw @@ -0,0 +1,1593 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "table-container", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1112, + "height": 600, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "toolbar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1112, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-input", + "type": "rectangle", + "x": 12, + "y": 10, + "width": 240, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-placeholder", + "type": "text", + "x": 24, + "y": 18, + "width": 80, + "height": 20, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Search...", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "filter-button-1", + "type": "rectangle", + "x": 264, + "y": 10, + "width": 100, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-button-1-text", + "type": "text", + "x": 284, + "y": 18, + "width": 60, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Status", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "filter-button-2", + "type": "rectangle", + "x": 376, + "y": 10, + "width": 100, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-button-2-text", + "type": "text", + "x": 396, + "y": 18, + "width": 60, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Category", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "view-options", + "type": "rectangle", + "x": 980, + "y": 10, + "width": 120, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "view-options-text", + "type": "text", + "x": 1000, + "y": 18, + "width": 80, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "View", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "table-header-row", + "type": "rectangle", + "x": 0, + "y": 56, + "width": 1112, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-checkbox", + "type": "rectangle", + "x": 16, + "y": 70, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "header-row-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-name", + "type": "text", + "x": 60, + "y": 72, + "width": 60, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Name", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "header-email", + "type": "text", + "x": 260, + "y": 72, + "width": 60, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Email", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "header-role", + "type": "text", + "x": 520, + "y": 72, + "width": 40, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Role", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "header-status", + "type": "text", + "x": 680, + "y": 72, + "width": 50, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Status", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "header-actions", + "type": "text", + "x": 1020, + "y": 72, + "width": 60, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Actions", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "data-row-1", + "type": "rectangle", + "x": 0, + "y": 104, + "width": 1112, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-checkbox", + "type": "rectangle", + "x": 16, + "y": 118, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-name", + "type": "text", + "x": 60, + "y": 120, + "width": 100, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "John Smith", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "row-1-email", + "type": "text", + "x": 260, + "y": 120, + "width": 140, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "john@example.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "row-1-role", + "type": "text", + "x": 520, + "y": 120, + "width": 50, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Admin", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "row-1-status-badge", + "type": "rectangle", + "x": 680, + "y": 114, + "width": 60, + "height": 28, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 14 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status-text", + "type": "text", + "x": 692, + "y": 120, + "width": 36, + "height": 16, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Active", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 10 + }, + { + "id": "data-row-2", + "type": "rectangle", + "x": 0, + "y": 152, + "width": 1112, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-checkbox", + "type": "rectangle", + "x": 16, + "y": 166, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-name", + "type": "text", + "x": 60, + "y": 168, + "width": 100, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Jane Doe", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "row-2-email", + "type": "text", + "x": 260, + "y": 168, + "width": 140, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "jane@example.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "row-2-role", + "type": "text", + "x": 520, + "y": 168, + "width": 50, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Editor", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "row-2-status-badge", + "type": "rectangle", + "x": 680, + "y": 162, + "width": 60, + "height": 28, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 14 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status-text", + "type": "text", + "x": 692, + "y": 168, + "width": 36, + "height": 16, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Active", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 10 + }, + { + "id": "data-row-3", + "type": "rectangle", + "x": 0, + "y": 200, + "width": 1112, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-checkbox", + "type": "rectangle", + "x": 16, + "y": 214, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-name", + "type": "text", + "x": 60, + "y": 216, + "width": 100, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Bob Wilson", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "row-3-email", + "type": "text", + "x": 260, + "y": 216, + "width": 140, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "bob@example.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "row-3-role", + "type": "text", + "x": 520, + "y": 216, + "width": 50, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Viewer", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "row-3-status-badge", + "type": "rectangle", + "x": 680, + "y": 210, + "width": 60, + "height": 28, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 14 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-status-text", + "type": "text", + "x": 688, + "y": 216, + "width": 44, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Inactive", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 10 + }, + { + "id": "data-row-4", + "type": "rectangle", + "x": 0, + "y": 248, + "width": 1112, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-4-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-checkbox", + "type": "rectangle", + "x": 16, + "y": 262, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-4-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-name", + "type": "text", + "x": 60, + "y": 264, + "width": 100, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-4-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Alice Brown", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "row-4-email", + "type": "text", + "x": 260, + "y": 264, + "width": 140, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-4-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "alice@example.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "row-4-role", + "type": "text", + "x": 520, + "y": 264, + "width": 50, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-4-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Editor", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "row-4-status-badge", + "type": "rectangle", + "x": 680, + "y": 258, + "width": 60, + "height": 28, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-4-group"], + "roundness": { "type": 3, "value": 14 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-status-text", + "type": "text", + "x": 692, + "y": 264, + "width": 36, + "height": 16, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-4-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Active", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 10 + }, + { + "id": "data-row-5", + "type": "rectangle", + "x": 0, + "y": 296, + "width": 1112, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-5-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-checkbox", + "type": "rectangle", + "x": 16, + "y": 310, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-5-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-name", + "type": "text", + "x": 60, + "y": 312, + "width": 100, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-5-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Charlie Davis", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "row-5-email", + "type": "text", + "x": 260, + "y": 312, + "width": 140, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-5-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "charlie@example.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "row-5-role", + "type": "text", + "x": 520, + "y": 312, + "width": 50, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-5-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Viewer", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "row-5-status-badge", + "type": "rectangle", + "x": 680, + "y": 306, + "width": 60, + "height": 28, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-5-group"], + "roundness": { "type": 3, "value": 14 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-status-text", + "type": "text", + "x": 692, + "y": 312, + "width": 36, + "height": 16, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-5-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Active", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 10 + }, + { + "id": "empty-rows-placeholder", + "type": "rectangle", + "x": 0, + "y": 344, + "width": 1112, + "height": 208, + "strokeColor": "transparent", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-bar", + "type": "rectangle", + "x": 0, + "y": 552, + "width": 1112, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-info", + "type": "text", + "x": 24, + "y": 568, + "width": 140, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Showing 1-10 of 100", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "page-prev", + "type": "rectangle", + "x": 820, + "y": 560, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-prev-arrow", + "type": "text", + "x": 830, + "y": 568, + "width": 12, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "<", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "page-1", + "type": "rectangle", + "x": 860, + "y": 560, + "width": 32, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-1-text", + "type": "text", + "x": 872, + "y": 568, + "width": 8, + "height": 16, + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "1", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "page-2", + "type": "rectangle", + "x": 900, + "y": 560, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-2-text", + "type": "text", + "x": 912, + "y": 568, + "width": 8, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "2", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "page-3", + "type": "rectangle", + "x": 940, + "y": 560, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-3-text", + "type": "text", + "x": 952, + "y": 568, + "width": 8, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "3", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "page-ellipsis", + "type": "text", + "x": 984, + "y": 568, + "width": 20, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "...", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "page-10", + "type": "rectangle", + "x": 1012, + "y": 560, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-10-text", + "type": "text", + "x": 1020, + "y": 568, + "width": 16, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "10", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "page-next", + "type": "rectangle", + "x": 1052, + "y": 560, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-next-arrow", + "type": "text", + "x": 1062, + "y": 568, + "width": 12, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": ">", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 12 + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/_templates/components/form.excalidraw b/.context/turbostarter-framework-context/wireframes/_templates/components/form.excalidraw new file mode 100644 index 0000000..8d2edd7 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/_templates/components/form.excalidraw @@ -0,0 +1,433 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "form-container", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 400, + "height": 500, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "form-title", + "type": "rectangle", + "x": 24, + "y": 24, + "width": 352, + "height": 28, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "form-description", + "type": "rectangle", + "x": 24, + "y": 60, + "width": 352, + "height": 20, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "label-1", + "type": "rectangle", + "x": 24, + "y": 100, + "width": 80, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "input-1", + "type": "rectangle", + "x": 24, + "y": 120, + "width": 352, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "label-2", + "type": "rectangle", + "x": 24, + "y": 172, + "width": 80, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "input-2", + "type": "rectangle", + "x": 24, + "y": 192, + "width": 352, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "label-3", + "type": "rectangle", + "x": 24, + "y": 244, + "width": 80, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "input-3", + "type": "rectangle", + "x": 24, + "y": 264, + "width": 352, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "eye-icon", + "type": "rectangle", + "x": 340, + "y": 276, + "width": 20, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "checkbox", + "type": "rectangle", + "x": 24, + "y": 316, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "checkbox-label", + "type": "rectangle", + "x": 52, + "y": 318, + "width": 100, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "forgot-link", + "type": "rectangle", + "x": 276, + "y": 318, + "width": 100, + "height": 16, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "primary-button", + "type": "rectangle", + "x": 24, + "y": 352, + "width": 352, + "height": 44, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-button", + "type": "rectangle", + "x": 24, + "y": 404, + "width": 352, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-left", + "type": "line", + "x": 24, + "y": 468, + "width": 140, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [140, 0]] + }, + { + "id": "divider-text", + "type": "rectangle", + "x": 172, + "y": 460, + "width": 56, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-right", + "type": "line", + "x": 236, + "y": 468, + "width": 140, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [140, 0]] + }, + { + "id": "oauth-google", + "type": "rectangle", + "x": 100, + "y": 484, + "width": 60, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-github", + "type": "rectangle", + "x": 170, + "y": 484, + "width": 60, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-apple", + "type": "rectangle", + "x": 240, + "y": 484, + "width": 60, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/_templates/components/header.excalidraw b/.context/turbostarter-framework-context/wireframes/_templates/components/header.excalidraw new file mode 100644 index 0000000..feba6a9 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/_templates/components/header.excalidraw @@ -0,0 +1,221 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "header-container", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "title-text", + "type": "text", + "x": 24, + "y": 14, + "width": 200, + "height": 24, + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group", "title-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Page Title", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18 + }, + { + "id": "description-text", + "type": "text", + "x": 24, + "y": 42, + "width": 300, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group", "title-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Brief description of this page", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 12 + }, + { + "id": "breadcrumb-area", + "type": "rectangle", + "x": 360, + "y": 22, + "width": 400, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group", "breadcrumb-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "breadcrumb-text", + "type": "text", + "x": 360, + "y": 22, + "width": 200, + "height": 20, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group", "breadcrumb-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Home > Section > Current", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 10 + }, + { + "id": "help-icon", + "type": "ellipse", + "x": 1000, + "y": 14, + "width": 36, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group", "actions-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "help-icon-text", + "type": "text", + "x": 1012, + "y": 22, + "width": 12, + "height": 20, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group", "actions-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "?", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 14 + }, + { + "id": "action-button", + "type": "rectangle", + "x": 1048, + "y": 14, + "width": 100, + "height": 36, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group", "actions-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "action-button-text", + "type": "text", + "x": 1068, + "y": 24, + "width": 60, + "height": 16, + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group", "actions-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Action", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 12 + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/_templates/components/modal.excalidraw b/.context/turbostarter-framework-context/wireframes/_templates/components/modal.excalidraw new file mode 100644 index 0000000..d8b4481 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/_templates/components/modal.excalidraw @@ -0,0 +1,335 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "backdrop", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 800, + "height": 600, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 30, + "groupIds": [], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "modal-container", + "type": "rectangle", + "x": 160, + "y": 140, + "width": 480, + "height": 320, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["modal-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-border", + "type": "line", + "x": 160, + "y": 196, + "width": 480, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["modal-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [480, 0]] + }, + { + "id": "modal-title", + "type": "rectangle", + "x": 184, + "y": 160, + "width": 200, + "height": 24, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["modal-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "close-button", + "type": "rectangle", + "x": 592, + "y": 156, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["modal-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "close-x-h", + "type": "line", + "x": 600, + "y": 164, + "width": 16, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["modal-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [16, 16]] + }, + { + "id": "close-x-v", + "type": "line", + "x": 616, + "y": 164, + "width": -16, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["modal-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [-16, 16]] + }, + { + "id": "content-desc-1", + "type": "rectangle", + "x": 184, + "y": 220, + "width": 432, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["modal-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-desc-2", + "type": "rectangle", + "x": 184, + "y": 244, + "width": 380, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["modal-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-desc-3", + "type": "rectangle", + "x": 184, + "y": 268, + "width": 300, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["modal-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "optional-input", + "type": "rectangle", + "x": 184, + "y": 304, + "width": 432, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["modal-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "footer-bg", + "type": "rectangle", + "x": 160, + "y": 396, + "width": 480, + "height": 64, + "strokeColor": "transparent", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["modal-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "footer-bg-top-mask", + "type": "rectangle", + "x": 160, + "y": 396, + "width": 480, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["modal-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "footer-border", + "type": "line", + "x": 160, + "y": 396, + "width": 480, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["modal-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [480, 0]] + }, + { + "id": "cancel-button", + "type": "rectangle", + "x": 408, + "y": 408, + "width": 100, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["modal-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "confirm-button", + "type": "rectangle", + "x": 516, + "y": 408, + "width": 100, + "height": 40, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["modal-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/_templates/components/sidebar.excalidraw b/.context/turbostarter-framework-context/wireframes/_templates/components/sidebar.excalidraw new file mode 100644 index 0000000..f241630 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/_templates/components/sidebar.excalidraw @@ -0,0 +1,441 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "sidebar-container", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-area", + "type": "rectangle", + "x": 24, + "y": 24, + "width": 232, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 44, + "y": 34, + "width": 80, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Logo", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 14 + }, + { + "id": "section-divider-1", + "type": "line", + "x": 24, + "y": 88, + "width": 232, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [232, 0]] + }, + { + "id": "nav-item-1-active", + "type": "rectangle", + "x": 24, + "y": 108, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-item-1-text", + "type": "text", + "x": 44, + "y": 118, + "width": 100, + "height": 20, + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Dashboard", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-item-2", + "type": "rectangle", + "x": 24, + "y": 160, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-item-2-text", + "type": "text", + "x": 44, + "y": 170, + "width": 100, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Browse MCPs", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-item-3", + "type": "rectangle", + "x": 24, + "y": 212, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-item-3-text", + "type": "text", + "x": 44, + "y": 222, + "width": 100, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Bundles", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-item-4", + "type": "rectangle", + "x": 24, + "y": 264, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-item-4-text", + "type": "text", + "x": 44, + "y": 274, + "width": 100, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "My Installs", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-item-5", + "type": "rectangle", + "x": 24, + "y": 316, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-item-5-text", + "type": "text", + "x": 44, + "y": 326, + "width": 100, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Settings", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-2", + "type": "line", + "x": 24, + "y": 812, + "width": 232, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [232, 0]] + }, + { + "id": "user-footer", + "type": "rectangle", + "x": 24, + "y": 824, + "width": 232, + "height": 64, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-avatar", + "type": "ellipse", + "x": 24, + "y": 836, + "width": 40, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-name", + "type": "text", + "x": 76, + "y": 840, + "width": 120, + "height": 16, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "John Doe", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 12 + }, + { + "id": "user-email", + "type": "text", + "x": 76, + "y": 858, + "width": 140, + "height": 14, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "john@example.com", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 10 + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/_templates/layouts/auth.excalidraw b/.context/turbostarter-framework-context/wireframes/_templates/layouts/auth.excalidraw new file mode 100644 index 0000000..de061fb --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/_templates/layouts/auth.excalidraw @@ -0,0 +1,271 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "left-column", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["left-column-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-column", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-column-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-placeholder", + "type": "rectangle", + "x": 300, + "y": 80, + "width": 120, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["left-column-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "form-container", + "type": "rectangle", + "x": 160, + "y": 250, + "width": 400, + "height": 400, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "form-title", + "type": "rectangle", + "x": 200, + "y": 290, + "width": 320, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "form-input-1", + "type": "rectangle", + "x": 200, + "y": 360, + "width": 320, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "form-input-2", + "type": "rectangle", + "x": 200, + "y": 420, + "width": 320, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "form-submit-button", + "type": "rectangle", + "x": 200, + "y": 500, + "width": 320, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "form-divider", + "type": "rectangle", + "x": 200, + "y": 564, + "width": 320, + "height": 2, + "strokeColor": "$border", + "backgroundColor": "$border", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "form-oauth-button", + "type": "rectangle", + "x": 200, + "y": 586, + "width": 320, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo", + "type": "rectangle", + "x": 1000, + "y": 400, + "width": 160, + "height": 60, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-column-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-tagline", + "type": "rectangle", + "x": 920, + "y": 480, + "width": 320, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-column-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/_templates/layouts/dashboard.excalidraw b/.context/turbostarter-framework-context/wireframes/_templates/layouts/dashboard.excalidraw new file mode 100644 index 0000000..dc3b044 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/_templates/layouts/dashboard.excalidraw @@ -0,0 +1,391 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-1", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-2", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-3", + "type": "rectangle", + "x": 20, + "y": 220, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-4", + "type": "rectangle", + "x": 20, + "y": 280, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "rectangle", + "x": 304, + "y": 20, + "width": 200, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-action-1", + "type": "rectangle", + "x": 1280, + "y": 16, + "width": 60, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-action-2", + "type": "rectangle", + "x": 1360, + "y": 16, + "width": 60, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-card-1", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 360, + "height": 180, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-card-2", + "type": "rectangle", + "x": 684, + "y": 88, + "width": 360, + "height": 180, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-card-3", + "type": "rectangle", + "x": 1064, + "y": 88, + "width": 360, + "height": 180, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-main-card", + "type": "rectangle", + "x": 304, + "y": 288, + "width": 1120, + "height": 400, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/_templates/layouts/marketing.excalidraw b/.context/turbostarter-framework-context/wireframes/_templates/layouts/marketing.excalidraw new file mode 100644 index 0000000..5a36076 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/_templates/layouts/marketing.excalidraw @@ -0,0 +1,451 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-logo", + "type": "rectangle", + "x": 40, + "y": 16, + "width": 100, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-nav-1", + "type": "rectangle", + "x": 580, + "y": 20, + "width": 80, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-nav-2", + "type": "rectangle", + "x": 680, + "y": 20, + "width": 80, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-nav-3", + "type": "rectangle", + "x": 780, + "y": 20, + "width": 80, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-auth-login", + "type": "rectangle", + "x": 1260, + "y": 16, + "width": 80, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-auth-signup", + "type": "rectangle", + "x": 1360, + "y": 16, + "width": 80, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 0, + "y": 64, + "width": 1440, + "height": 716, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "hero-headline", + "type": "rectangle", + "x": 320, + "y": 160, + "width": 800, + "height": 60, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "hero-subheadline", + "type": "rectangle", + "x": 420, + "y": 240, + "width": 600, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "hero-cta-primary", + "type": "rectangle", + "x": 560, + "y": 320, + "width": 160, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "hero-cta-secondary", + "type": "rectangle", + "x": 740, + "y": 320, + "width": 140, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "hero-image", + "type": "rectangle", + "x": 220, + "y": 420, + "width": 1000, + "height": 320, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "footer", + "type": "rectangle", + "x": 0, + "y": 780, + "width": 1440, + "height": 120, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["footer-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "footer-logo", + "type": "rectangle", + "x": 40, + "y": 800, + "width": 100, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["footer-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "footer-links-1", + "type": "rectangle", + "x": 400, + "y": 800, + "width": 140, + "height": 80, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["footer-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "footer-links-2", + "type": "rectangle", + "x": 560, + "y": 800, + "width": 140, + "height": 80, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["footer-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "footer-links-3", + "type": "rectangle", + "x": 720, + "y": 800, + "width": 140, + "height": 80, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["footer-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "footer-social-1", + "type": "rectangle", + "x": 1280, + "y": 820, + "width": 40, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["footer-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "footer-social-2", + "type": "rectangle", + "x": 1340, + "y": 820, + "width": 40, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["footer-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "footer-social-3", + "type": "rectangle", + "x": 1400, + "y": 820, + "width": 40, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["footer-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/apply-theme.js b/.context/turbostarter-framework-context/wireframes/apply-theme.js new file mode 100644 index 0000000..5227f84 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/apply-theme.js @@ -0,0 +1,310 @@ +#!/usr/bin/env node + +/** + * Excalidraw Theme Applicator + * + * This script applies color themes to Excalidraw wireframe files by replacing + * color tokens (like $background, $primary, etc.) with actual hex values. + * + * Usage: + * node apply-theme.js [output.excalidraw] + * + * Example: + * node apply-theme.js wireframe.excalidraw orange-light themed-wireframe.excalidraw + * + * The script expects a wireframe-themes.json file in the same directory with + * the following structure: + * + * { + * "themes": { + * "orange-light": { + * "background": "#ffffff", + * "primary": "#f97316", + * "secondary": "#fed7aa", + * "accent": "#ea580c", + * "text": "#1f2937", + * "muted": "#9ca3af", + * "border": "#e5e7eb", + * "surface": "#f9fafb" + * }, + * "blue-dark": { + * "background": "#0f172a", + * ... + * } + * } + * } + */ + +const fs = require("fs"); +const path = require("path"); + +// --------------------------------------------------------------------------- +// Configuration +// --------------------------------------------------------------------------- + +const SCRIPT_DIR = __dirname; +const THEMES_FILE = path.join(SCRIPT_DIR, "wireframe-themes.json"); + +// Color properties to search for in Excalidraw elements +const COLOR_PROPERTIES = ["strokeColor", "backgroundColor"]; + +// --------------------------------------------------------------------------- +// Helper Functions +// --------------------------------------------------------------------------- + +/** + * Load and parse the themes configuration file + * @returns {Object} The themes configuration object + */ +function loadThemes() { + if (!fs.existsSync(THEMES_FILE)) { + console.error(`Error: Themes file not found at ${THEMES_FILE}`); + console.error("\nPlease create a wireframe-themes.json file with the following structure:"); + console.error(` +{ + "themes": { + "theme-name": { + "background": "#ffffff", + "primary": "#f97316", + "secondary": "#fed7aa", + "accent": "#ea580c", + "text": "#1f2937", + "muted": "#9ca3af", + "border": "#e5e7eb", + "surface": "#f9fafb" + } + } +} +`); + process.exit(1); + } + + try { + const content = fs.readFileSync(THEMES_FILE, "utf-8"); + return JSON.parse(content); + } catch (error) { + console.error(`Error: Failed to parse themes file: ${error.message}`); + process.exit(1); + } +} + +/** + * List all available themes from the configuration + * @param {Object} themesConfig - The themes configuration object + */ +function listAvailableThemes(themesConfig) { + const themes = Object.keys(themesConfig.themes || {}); + + if (themes.length === 0) { + console.error("No themes found in wireframe-themes.json"); + return; + } + + console.log("\nAvailable themes:"); + themes.forEach((theme) => { + console.log(` - ${theme}`); + }); + console.log(""); +} + +/** + * Replace a color token with the corresponding theme color + * @param {string} value - The color value (may be a token like "$primary") + * @param {Object} themeColors - The color mapping for the selected theme + * @returns {string} The resolved hex color or the original value if not a token + */ +function resolveColorToken(value, themeColors) { + // Check if the value is a color token (starts with $) + if (typeof value !== "string" || !value.startsWith("$")) { + return value; + } + + // Extract the token name (remove the $ prefix) + const tokenName = value.substring(1); + + // Look up the color in the theme + if (themeColors.hasOwnProperty(tokenName)) { + return themeColors[tokenName]; + } + + // Token not found in theme - warn but keep original + console.warn(`Warning: Color token "${value}" not found in theme`); + return value; +} + +/** + * Recursively process an element and its children, replacing color tokens + * @param {Object} element - An Excalidraw element + * @param {Object} themeColors - The color mapping for the selected theme + * @param {Object} stats - Statistics object to track replacements + * @returns {Object} The element with colors replaced + */ +function processElement(element, themeColors, stats) { + if (!element || typeof element !== "object") { + return element; + } + + // Handle arrays (like the elements array or grouped elements) + if (Array.isArray(element)) { + return element.map((item) => processElement(item, themeColors, stats)); + } + + // Process the current object + const processed = { ...element }; + + // Check and replace color properties + for (const prop of COLOR_PROPERTIES) { + if (processed.hasOwnProperty(prop) && typeof processed[prop] === "string") { + const originalValue = processed[prop]; + const newValue = resolveColorToken(originalValue, themeColors); + + if (originalValue !== newValue) { + processed[prop] = newValue; + stats.replacements++; + stats.details.push({ + property: prop, + from: originalValue, + to: newValue, + }); + } + } + } + + // Recursively process nested objects and arrays + for (const key of Object.keys(processed)) { + if (typeof processed[key] === "object" && processed[key] !== null) { + processed[key] = processElement(processed[key], themeColors, stats); + } + } + + return processed; +} + +/** + * Generate a default output filename based on input and theme + * @param {string} inputFile - The input file path + * @param {string} themeName - The theme name + * @returns {string} The generated output file path + */ +function generateOutputFilename(inputFile, themeName) { + const dir = path.dirname(inputFile); + const ext = path.extname(inputFile); + const base = path.basename(inputFile, ext); + + return path.join(dir, `${base}-${themeName}${ext}`); +} + +/** + * Display usage information + */ +function showUsage() { + console.log(` +Excalidraw Theme Applicator + +Usage: + node apply-theme.js [output.excalidraw] + +Arguments: + input.excalidraw - Path to the input Excalidraw JSON file + theme-name - Name of the theme to apply + output.excalidraw - Optional output file path (default: input-themename.excalidraw) + +Examples: + node apply-theme.js wireframe.excalidraw orange-light + node apply-theme.js wireframe.excalidraw blue-dark themed-wireframe.excalidraw + +Color Tokens: + The script replaces tokens like $background, $primary, etc. in strokeColor + and backgroundColor properties with hex values from the selected theme. +`); +} + +// --------------------------------------------------------------------------- +// Main Script +// --------------------------------------------------------------------------- + +function main() { + // Parse command line arguments + const args = process.argv.slice(2); + + if (args.length < 2) { + showUsage(); + + // Try to show available themes if the file exists + if (fs.existsSync(THEMES_FILE)) { + const themesConfig = loadThemes(); + listAvailableThemes(themesConfig); + } + + process.exit(1); + } + + const inputFile = args[0]; + const themeName = args[1]; + const outputFile = args[2] || generateOutputFilename(inputFile, themeName); + + // Validate input file exists + if (!fs.existsSync(inputFile)) { + console.error(`Error: Input file not found: ${inputFile}`); + process.exit(1); + } + + // Load themes configuration + const themesConfig = loadThemes(); + + // Validate theme exists + if (!themesConfig.themes || !themesConfig.themes[themeName]) { + console.error(`Error: Theme "${themeName}" not found`); + listAvailableThemes(themesConfig); + process.exit(1); + } + + const themeColors = themesConfig.themes[themeName]; + + // Load and parse the Excalidraw file + let excalidrawData; + try { + const content = fs.readFileSync(inputFile, "utf-8"); + excalidrawData = JSON.parse(content); + } catch (error) { + console.error(`Error: Failed to parse input file: ${error.message}`); + process.exit(1); + } + + // Track statistics + const stats = { + replacements: 0, + details: [], + }; + + // Process the Excalidraw data + console.log(`Applying theme "${themeName}" to ${inputFile}...`); + + const themedData = processElement(excalidrawData, themeColors, stats); + + // Write the output file + try { + const outputContent = JSON.stringify(themedData, null, 2); + fs.writeFileSync(outputFile, outputContent, "utf-8"); + } catch (error) { + console.error(`Error: Failed to write output file: ${error.message}`); + process.exit(1); + } + + // Report results + console.log(`\nTheme applied successfully!`); + console.log(` Input: ${inputFile}`); + console.log(` Output: ${outputFile}`); + console.log(` Theme: ${themeName}`); + console.log(` Replacements: ${stats.replacements}`); + + if (stats.replacements > 0 && args.includes("--verbose")) { + console.log("\nDetails:"); + stats.details.forEach((detail) => { + console.log(` ${detail.property}: ${detail.from} -> ${detail.to}`); + }); + } +} + +// Run the script +main(); diff --git a/.context/turbostarter-framework-context/wireframes/high-fidelity/auth-forgot-password.excalidraw b/.context/turbostarter-framework-context/wireframes/high-fidelity/auth-forgot-password.excalidraw new file mode 100644 index 0000000..4764acb --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/high-fidelity/auth-forgot-password.excalidraw @@ -0,0 +1,831 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "left-column", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["left-column-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-column", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-column-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-gradient-overlay", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": ["right-column-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-pattern-circle-1", + "type": "ellipse", + "x": 800, + "y": 100, + "width": 200, + "height": 200, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 15, + "groupIds": ["right-column-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-pattern-circle-2", + "type": "ellipse", + "x": 1200, + "y": 600, + "width": 300, + "height": 300, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 10, + "groupIds": ["right-column-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-icon", + "type": "rectangle", + "x": 320, + "y": 200, + "width": 40, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["logo-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-icon-inner", + "type": "rectangle", + "x": 330, + "y": 210, + "width": 20, + "height": 20, + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["logo-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 368, + "y": 208, + "width": 80, + "height": 24, + "text": "MCPGet", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["logo-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "lock-icon-bg", + "type": "ellipse", + "x": 336, + "y": 290, + "width": 48, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["icon-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "lock-icon-shackle", + "type": "rectangle", + "x": 350, + "y": 298, + "width": 20, + "height": 14, + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["icon-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "lock-icon-body", + "type": "rectangle", + "x": 346, + "y": 310, + "width": 28, + "height": 20, + "strokeColor": "$muted-foreground", + "backgroundColor": "$muted-foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["icon-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "lock-icon-keyhole", + "type": "ellipse", + "x": 356, + "y": 316, + "width": 8, + "height": 8, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["icon-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "forgot-password-title-text", + "type": "text", + "x": 160, + "y": 370, + "width": 400, + "height": 32, + "text": "Forgot your password?", + "fontSize": 24, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["title-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "forgot-password-description-text", + "type": "text", + "x": 160, + "y": 410, + "width": 400, + "height": 40, + "text": "No worries! Enter your email address and we'll send you a link to reset your password.", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["title-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-label", + "type": "text", + "x": 160, + "y": 478, + "width": 100, + "height": 16, + "text": "Email address", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["email-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-input", + "type": "rectangle", + "x": 160, + "y": 502, + "width": 400, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["email-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-icon", + "type": "rectangle", + "x": 172, + "y": 516, + "width": 16, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["email-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-placeholder", + "type": "text", + "x": 200, + "y": 514, + "width": 200, + "height": 20, + "text": "you@example.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["email-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "send-reset-button", + "type": "rectangle", + "x": 160, + "y": 570, + "width": 400, + "height": 44, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["button-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "send-reset-button-text", + "type": "text", + "x": 160, + "y": 582, + "width": 400, + "height": 20, + "text": "Send reset link", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$primary-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["button-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "back-arrow-line", + "type": "rectangle", + "x": 320, + "y": 658, + "width": 16, + "height": 2, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["back-link-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "back-arrow-head-top", + "type": "rectangle", + "x": 320, + "y": 654, + "width": 6, + "height": 2, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["back-link-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "back-arrow-head-bottom", + "type": "rectangle", + "x": 320, + "y": 662, + "width": 6, + "height": 2, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["back-link-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "back-to-login-link-text", + "type": "text", + "x": 344, + "y": 650, + "width": 100, + "height": 20, + "text": "Back to sign in", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["back-link-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "back-to-login-underline", + "type": "rectangle", + "x": 344, + "y": 668, + "width": 92, + "height": 1, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 40, + "groupIds": ["back-link-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "success-state-container", + "type": "rectangle", + "x": 160, + "y": 720, + "width": 400, + "height": 80, + "strokeColor": "$success", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 10, + "groupIds": ["success-state-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "success-state-border", + "type": "rectangle", + "x": 160, + "y": 720, + "width": 400, + "height": 80, + "strokeColor": "$success", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["success-state-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "success-icon", + "type": "ellipse", + "x": 180, + "y": 740, + "width": 40, + "height": 40, + "strokeColor": "$success", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 20, + "groupIds": ["success-state-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "success-check", + "type": "rectangle", + "x": 192, + "y": 758, + "width": 16, + "height": 2, + "strokeColor": "$success", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["success-state-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "success-title", + "type": "text", + "x": 236, + "y": 738, + "width": 200, + "height": 20, + "text": "Check your email", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$success", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["success-state-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "success-description", + "type": "text", + "x": 236, + "y": 762, + "width": 300, + "height": 20, + "text": "We've sent a password reset link to your email", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["success-state-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-container", + "type": "rectangle", + "x": 880, + "y": 350, + "width": 400, + "height": 200, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-branding-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo-bg", + "type": "rectangle", + "x": 1020, + "y": 380, + "width": 120, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-branding-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo-icon", + "type": "rectangle", + "x": 1036, + "y": 394, + "width": 20, + "height": 20, + "strokeColor": "$primary-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-branding-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo-text", + "type": "text", + "x": 1064, + "y": 394, + "width": 60, + "height": 20, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$primary-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-branding-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-tagline", + "type": "text", + "x": 880, + "y": 452, + "width": 400, + "height": 24, + "text": "Secure access recovery", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-branding-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-description", + "type": "text", + "x": 920, + "y": 484, + "width": 320, + "height": 40, + "text": "Get back into your account quickly and securely with our password recovery system", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-branding-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/high-fidelity/auth-join-org.excalidraw b/.context/turbostarter-framework-context/wireframes/high-fidelity/auth-join-org.excalidraw new file mode 100644 index 0000000..88ca15f --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/high-fidelity/auth-join-org.excalidraw @@ -0,0 +1,811 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "left-column", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["left-column-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-column", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-column-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-gradient-overlay", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": ["right-column-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-pattern-circle-1", + "type": "ellipse", + "x": 800, + "y": 100, + "width": 200, + "height": 200, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 15, + "groupIds": ["right-column-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-pattern-circle-2", + "type": "ellipse", + "x": 1200, + "y": 600, + "width": 300, + "height": 300, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 10, + "groupIds": ["right-column-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-icon", + "type": "rectangle", + "x": 320, + "y": 120, + "width": 40, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["logo-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-icon-inner", + "type": "rectangle", + "x": 330, + "y": 130, + "width": 20, + "height": 20, + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["logo-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 368, + "y": 128, + "width": 80, + "height": 24, + "text": "MCPGet", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["logo-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "join-org-title-text", + "type": "text", + "x": 160, + "y": 200, + "width": 400, + "height": 32, + "text": "Join Organization", + "fontSize": 24, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["title-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "join-org-subtitle-text", + "type": "text", + "x": 160, + "y": 240, + "width": 400, + "height": 20, + "text": "You've been invited to join an organization", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["title-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "invitation-card-shadow", + "type": "rectangle", + "x": 163, + "y": 303, + "width": 394, + "height": 264, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 5, + "groupIds": ["invitation-card-group"], + "roundness": { "type": 3, "value": 16 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "invitation-card", + "type": "rectangle", + "x": 160, + "y": 300, + "width": 400, + "height": 270, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["invitation-card-group"], + "roundness": { "type": 3, "value": 16 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "org-logo-bg", + "type": "rectangle", + "x": 328, + "y": 332, + "width": 64, + "height": 64, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["invitation-card-group"], + "roundness": { "type": 3, "value": 16 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "org-logo-text", + "type": "text", + "x": 328, + "y": 352, + "width": 64, + "height": 24, + "text": "AC", + "fontSize": 22, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$primary-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["invitation-card-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "org-name-text", + "type": "text", + "x": 160, + "y": 420, + "width": 400, + "height": 28, + "text": "Acme Corporation", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["invitation-card-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "inviter-name-text", + "type": "text", + "x": 160, + "y": 456, + "width": 400, + "height": 16, + "text": "Invited by John Doe (john@acmecorp.com)", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["invitation-card-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "role-badge", + "type": "rectangle", + "x": 310, + "y": 492, + "width": 100, + "height": 28, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["role-badge-group"], + "roundness": { "type": 3, "value": 14 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "role-icon", + "type": "ellipse", + "x": 322, + "y": 500, + "width": 12, + "height": 12, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 40, + "groupIds": ["role-badge-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "role-badge-text", + "type": "text", + "x": 340, + "y": 498, + "width": 60, + "height": 20, + "text": "Member", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["role-badge-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "invitation-expires-text", + "type": "text", + "x": 160, + "y": 536, + "width": 400, + "height": 16, + "text": "This invitation expires in 7 days", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["invitation-card-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "accept-button", + "type": "rectangle", + "x": 160, + "y": 600, + "width": 400, + "height": 44, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["button-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "accept-button-icon", + "type": "ellipse", + "x": 292, + "y": 614, + "width": 16, + "height": 16, + "strokeColor": "$primary-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["button-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "accept-button-text", + "type": "text", + "x": 316, + "y": 612, + "width": 150, + "height": 20, + "text": "Accept invitation", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$primary-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["button-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "decline-button", + "type": "rectangle", + "x": 160, + "y": 660, + "width": 400, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["decline-button-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "decline-button-text", + "type": "text", + "x": 160, + "y": 672, + "width": 400, + "height": 20, + "text": "Decline", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["decline-button-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "wrong-account-text", + "type": "text", + "x": 248, + "y": 730, + "width": 120, + "height": 16, + "text": "Wrong account?", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["links-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sign-out-link-text", + "type": "text", + "x": 374, + "y": 730, + "width": 100, + "height": 16, + "text": "Sign out", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["links-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sign-out-link-underline", + "type": "rectangle", + "x": 374, + "y": 746, + "width": 52, + "height": 1, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 40, + "groupIds": ["links-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logged-in-as-text", + "type": "text", + "x": 160, + "y": 770, + "width": 400, + "height": 14, + "text": "Logged in as user@example.com", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["links-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-container", + "type": "rectangle", + "x": 880, + "y": 350, + "width": 400, + "height": 200, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-branding-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo-bg", + "type": "rectangle", + "x": 1020, + "y": 380, + "width": 120, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-branding-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo-icon", + "type": "rectangle", + "x": 1036, + "y": 394, + "width": 20, + "height": 20, + "strokeColor": "$primary-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-branding-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo-text", + "type": "text", + "x": 1064, + "y": 394, + "width": 60, + "height": 20, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$primary-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-branding-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-tagline", + "type": "text", + "x": 880, + "y": 452, + "width": 400, + "height": 24, + "text": "Collaborate with your team", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-branding-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-description", + "type": "text", + "x": 920, + "y": 484, + "width": 320, + "height": 40, + "text": "Share MCP configurations across your organization and work together seamlessly", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-branding-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/high-fidelity/auth-login.excalidraw b/.context/turbostarter-framework-context/wireframes/high-fidelity/auth-login.excalidraw new file mode 100644 index 0000000..a31b36d --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/high-fidelity/auth-login.excalidraw @@ -0,0 +1,1041 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "left-column", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["left-column-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-column", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-column-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-gradient-overlay", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": ["right-column-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-pattern-circle-1", + "type": "ellipse", + "x": 800, + "y": 100, + "width": 200, + "height": 200, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 15, + "groupIds": ["right-column-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-pattern-circle-2", + "type": "ellipse", + "x": 1200, + "y": 600, + "width": 300, + "height": 300, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 10, + "groupIds": ["right-column-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "form-container", + "type": "rectangle", + "x": 160, + "y": 150, + "width": 400, + "height": 600, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-container-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-icon", + "type": "rectangle", + "x": 320, + "y": 150, + "width": 40, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["logo-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-icon-inner", + "type": "rectangle", + "x": 330, + "y": 160, + "width": 20, + "height": 20, + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["logo-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 368, + "y": 158, + "width": 80, + "height": 24, + "text": "MCPGet", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["logo-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-title-text", + "type": "text", + "x": 160, + "y": 234, + "width": 400, + "height": 32, + "text": "Welcome back", + "fontSize": 24, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["title-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-subtitle-text", + "type": "text", + "x": 160, + "y": 274, + "width": 400, + "height": 20, + "text": "Sign in to your account to continue", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["title-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-label", + "type": "text", + "x": 160, + "y": 326, + "width": 100, + "height": 16, + "text": "Email address", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["email-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-input", + "type": "rectangle", + "x": 160, + "y": 350, + "width": 400, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["email-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-icon", + "type": "rectangle", + "x": 172, + "y": 364, + "width": 16, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["email-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-placeholder", + "type": "text", + "x": 200, + "y": 362, + "width": 200, + "height": 20, + "text": "you@example.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["email-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-label", + "type": "text", + "x": 160, + "y": 410, + "width": 80, + "height": 16, + "text": "Password", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["password-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-input", + "type": "rectangle", + "x": 160, + "y": 434, + "width": 400, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["password-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-lock-icon", + "type": "rectangle", + "x": 172, + "y": 448, + "width": 16, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["password-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-placeholder", + "type": "text", + "x": 200, + "y": 446, + "width": 200, + "height": 20, + "text": "Enter your password", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["password-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-eye-icon-outer", + "type": "ellipse", + "x": 528, + "y": 448, + "width": 20, + "height": 14, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["password-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-eye-icon-inner", + "type": "ellipse", + "x": 534, + "y": 451, + "width": 8, + "height": 8, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["password-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "forgot-password-link", + "type": "text", + "x": 440, + "y": 494, + "width": 120, + "height": 16, + "text": "Forgot password?", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "forgot-password-underline", + "type": "rectangle", + "x": 450, + "y": 510, + "width": 108, + "height": 1, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 40, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "login-button", + "type": "rectangle", + "x": 160, + "y": 534, + "width": 400, + "height": 44, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["button-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "login-button-text", + "type": "text", + "x": 160, + "y": 546, + "width": 400, + "height": 20, + "text": "Sign in", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$primary-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["button-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-line-left", + "type": "rectangle", + "x": 160, + "y": 610, + "width": 170, + "height": 1, + "strokeColor": "$border", + "backgroundColor": "$border", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["divider-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-text", + "type": "text", + "x": 340, + "y": 602, + "width": 40, + "height": 16, + "text": "or", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["divider-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-line-right", + "type": "rectangle", + "x": 390, + "y": 610, + "width": 170, + "height": 1, + "strokeColor": "$border", + "backgroundColor": "$border", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["divider-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-google", + "type": "rectangle", + "x": 160, + "y": 642, + "width": 125, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-google-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-google-icon", + "type": "ellipse", + "x": 194, + "y": 654, + "width": 20, + "height": 20, + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-google-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-google-text", + "type": "text", + "x": 220, + "y": 654, + "width": 50, + "height": 20, + "text": "Google", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-google-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-github", + "type": "rectangle", + "x": 297, + "y": 642, + "width": 125, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-github-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-github-icon", + "type": "ellipse", + "x": 331, + "y": 654, + "width": 20, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-github-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-github-text", + "type": "text", + "x": 357, + "y": 654, + "width": 50, + "height": 20, + "text": "GitHub", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-github-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-apple", + "type": "rectangle", + "x": 434, + "y": 642, + "width": 126, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-apple-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-apple-icon", + "type": "ellipse", + "x": 468, + "y": 654, + "width": 20, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-apple-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-apple-text", + "type": "text", + "x": 494, + "y": 654, + "width": 40, + "height": 20, + "text": "Apple", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-apple-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "signup-text-prefix", + "type": "text", + "x": 248, + "y": 718, + "width": 160, + "height": 16, + "text": "Don't have an account?", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["signup-link-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "signup-link-text", + "type": "text", + "x": 414, + "y": 718, + "width": 60, + "height": 16, + "text": "Sign up", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["signup-link-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "signup-link-underline", + "type": "rectangle", + "x": 414, + "y": 734, + "width": 52, + "height": 1, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 40, + "groupIds": ["signup-link-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-container", + "type": "rectangle", + "x": 880, + "y": 350, + "width": 400, + "height": 200, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-branding-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo-bg", + "type": "rectangle", + "x": 1020, + "y": 380, + "width": 120, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-branding-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo-icon", + "type": "rectangle", + "x": 1036, + "y": 394, + "width": 20, + "height": 20, + "strokeColor": "$primary-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-branding-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo-text", + "type": "text", + "x": 1064, + "y": 394, + "width": 60, + "height": 20, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$primary-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-branding-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-tagline", + "type": "text", + "x": 880, + "y": 452, + "width": 400, + "height": 24, + "text": "Discover and install MCPs with ease", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-branding-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-description", + "type": "text", + "x": 920, + "y": 484, + "width": 320, + "height": 40, + "text": "Your one-stop solution for managing Model Context Protocol configurations", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-branding-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/high-fidelity/auth-register.excalidraw b/.context/turbostarter-framework-context/wireframes/high-fidelity/auth-register.excalidraw new file mode 100644 index 0000000..8ad4d38 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/high-fidelity/auth-register.excalidraw @@ -0,0 +1,1401 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "left-column", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["left-column-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-column", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-column-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-gradient-overlay", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": ["right-column-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-pattern-circle-1", + "type": "ellipse", + "x": 800, + "y": 100, + "width": 200, + "height": 200, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 15, + "groupIds": ["right-column-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-pattern-circle-2", + "type": "ellipse", + "x": 1200, + "y": 600, + "width": 300, + "height": 300, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 10, + "groupIds": ["right-column-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-icon", + "type": "rectangle", + "x": 320, + "y": 60, + "width": 40, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["logo-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-icon-inner", + "type": "rectangle", + "x": 330, + "y": 70, + "width": 20, + "height": 20, + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["logo-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 368, + "y": 68, + "width": 80, + "height": 24, + "text": "MCPGet", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["logo-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "create-account-title-text", + "type": "text", + "x": 160, + "y": 130, + "width": 400, + "height": 32, + "text": "Create your account", + "fontSize": 24, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["title-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "create-account-subtitle-text", + "type": "text", + "x": 160, + "y": 170, + "width": 400, + "height": 20, + "text": "Start your journey with MCPGet today", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["title-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-label", + "type": "text", + "x": 160, + "y": 214, + "width": 80, + "height": 16, + "text": "Full name", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["name-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-input", + "type": "rectangle", + "x": 160, + "y": 238, + "width": 400, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["name-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-user-icon", + "type": "ellipse", + "x": 172, + "y": 252, + "width": 16, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["name-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-placeholder", + "type": "text", + "x": 200, + "y": 250, + "width": 200, + "height": 20, + "text": "John Doe", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["name-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-label", + "type": "text", + "x": 160, + "y": 298, + "width": 100, + "height": 16, + "text": "Email address", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["email-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-input", + "type": "rectangle", + "x": 160, + "y": 322, + "width": 400, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["email-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-icon", + "type": "rectangle", + "x": 172, + "y": 336, + "width": 16, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["email-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-placeholder", + "type": "text", + "x": 200, + "y": 334, + "width": 200, + "height": 20, + "text": "you@example.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["email-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-label", + "type": "text", + "x": 160, + "y": 382, + "width": 80, + "height": 16, + "text": "Password", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["password-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-input", + "type": "rectangle", + "x": 160, + "y": 406, + "width": 400, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["password-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-lock-icon", + "type": "rectangle", + "x": 172, + "y": 420, + "width": 16, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["password-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-placeholder", + "type": "text", + "x": 200, + "y": 418, + "width": 200, + "height": 20, + "text": "Create a strong password", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["password-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-eye-icon-outer", + "type": "ellipse", + "x": 528, + "y": 421, + "width": 20, + "height": 14, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["password-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-eye-icon-inner", + "type": "ellipse", + "x": 534, + "y": 424, + "width": 8, + "height": 8, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["password-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-strength-bg", + "type": "rectangle", + "x": 160, + "y": 458, + "width": 400, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 30, + "groupIds": ["password-strength-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-strength-fill", + "type": "rectangle", + "x": 160, + "y": 458, + "width": 200, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["password-strength-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-strength-text", + "type": "text", + "x": 160, + "y": 468, + "width": 120, + "height": 14, + "text": "Password strength: Good", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$success", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["password-strength-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "confirm-password-label", + "type": "text", + "x": 160, + "y": 494, + "width": 120, + "height": 16, + "text": "Confirm password", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["confirm-password-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "confirm-password-input", + "type": "rectangle", + "x": 160, + "y": 518, + "width": 400, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["confirm-password-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "confirm-password-lock-icon", + "type": "rectangle", + "x": 172, + "y": 532, + "width": 16, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["confirm-password-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "confirm-password-placeholder", + "type": "text", + "x": 200, + "y": 530, + "width": 200, + "height": 20, + "text": "Confirm your password", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["confirm-password-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "confirm-password-check-icon", + "type": "ellipse", + "x": 528, + "y": 532, + "width": 16, + "height": 16, + "strokeColor": "$success", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["confirm-password-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "terms-checkbox", + "type": "rectangle", + "x": 160, + "y": 586, + "width": 18, + "height": 18, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["terms-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "terms-text", + "type": "text", + "x": 186, + "y": 585, + "width": 130, + "height": 20, + "text": "I agree to the", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["terms-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "terms-link", + "type": "text", + "x": 282, + "y": 585, + "width": 100, + "height": 20, + "text": "Terms of Service", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["terms-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "terms-link-underline", + "type": "rectangle", + "x": 282, + "y": 603, + "width": 95, + "height": 1, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 40, + "groupIds": ["terms-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "terms-and-text", + "type": "text", + "x": 382, + "y": 585, + "width": 30, + "height": 20, + "text": "and", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["terms-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "privacy-link", + "type": "text", + "x": 416, + "y": 585, + "width": 90, + "height": 20, + "text": "Privacy Policy", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["terms-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "privacy-link-underline", + "type": "rectangle", + "x": 416, + "y": 603, + "width": 82, + "height": 1, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 40, + "groupIds": ["terms-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "create-account-button", + "type": "rectangle", + "x": 160, + "y": 630, + "width": 400, + "height": 44, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["button-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "create-account-button-text", + "type": "text", + "x": 160, + "y": 642, + "width": 400, + "height": 20, + "text": "Create account", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$primary-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["button-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-line-left", + "type": "rectangle", + "x": 160, + "y": 706, + "width": 170, + "height": 1, + "strokeColor": "$border", + "backgroundColor": "$border", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["divider-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-text", + "type": "text", + "x": 340, + "y": 698, + "width": 40, + "height": 16, + "text": "or", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["divider-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-line-right", + "type": "rectangle", + "x": 390, + "y": 706, + "width": 170, + "height": 1, + "strokeColor": "$border", + "backgroundColor": "$border", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["divider-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-google", + "type": "rectangle", + "x": 160, + "y": 734, + "width": 125, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-google-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-google-icon", + "type": "ellipse", + "x": 194, + "y": 746, + "width": 20, + "height": 20, + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-google-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-google-text", + "type": "text", + "x": 220, + "y": 746, + "width": 50, + "height": 20, + "text": "Google", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-google-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-github", + "type": "rectangle", + "x": 297, + "y": 734, + "width": 125, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-github-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-github-icon", + "type": "ellipse", + "x": 331, + "y": 746, + "width": 20, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-github-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-github-text", + "type": "text", + "x": 357, + "y": 746, + "width": 50, + "height": 20, + "text": "GitHub", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-github-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-apple", + "type": "rectangle", + "x": 434, + "y": 734, + "width": 126, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-apple-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-apple-icon", + "type": "ellipse", + "x": 468, + "y": 746, + "width": 20, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-apple-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-apple-text", + "type": "text", + "x": 494, + "y": 746, + "width": 40, + "height": 20, + "text": "Apple", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-apple-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "login-text-prefix", + "type": "text", + "x": 232, + "y": 810, + "width": 170, + "height": 16, + "text": "Already have an account?", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["login-link-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "login-link-text", + "type": "text", + "x": 410, + "y": 810, + "width": 50, + "height": 16, + "text": "Sign in", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["login-link-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "login-link-underline", + "type": "rectangle", + "x": 410, + "y": 826, + "width": 44, + "height": 1, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 40, + "groupIds": ["login-link-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-container", + "type": "rectangle", + "x": 880, + "y": 350, + "width": 400, + "height": 200, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-branding-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo-bg", + "type": "rectangle", + "x": 1020, + "y": 380, + "width": 120, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-branding-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo-icon", + "type": "rectangle", + "x": 1036, + "y": 394, + "width": 20, + "height": 20, + "strokeColor": "$primary-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-branding-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo-text", + "type": "text", + "x": 1064, + "y": 394, + "width": 60, + "height": 20, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$primary-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-branding-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-tagline", + "type": "text", + "x": 880, + "y": 452, + "width": 400, + "height": 24, + "text": "Join thousands of developers", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-branding-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-description", + "type": "text", + "x": 920, + "y": 484, + "width": 320, + "height": 40, + "text": "Manage your MCP configurations effortlessly with our powerful toolkit", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-branding-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/high-fidelity/dashboard-admin.excalidraw b/.context/turbostarter-framework-context/wireframes/high-fidelity/dashboard-admin.excalidraw new file mode 100644 index 0000000..660af52 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/high-fidelity/dashboard-admin.excalidraw @@ -0,0 +1,2456 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 24, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-text", + "type": "text", + "x": 36, + "y": 24, + "width": 96, + "height": 18, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-admin-badge", + "type": "rectangle", + "x": 160, + "y": 22, + "width": 52, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-admin-badge-text", + "type": "text", + "x": 168, + "y": 26, + "width": 36, + "height": 12, + "text": "Admin", + "fontSize": 9, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-home", + "type": "rectangle", + "x": 24, + "y": 100, + "width": 232, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-home-icon", + "type": "rectangle", + "x": 36, + "y": 110, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-home-icon-house", + "type": "rectangle", + "x": 40, + "y": 118, + "width": 12, + "height": 8, + "strokeColor": "transparent", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-home-icon-roof", + "type": "rectangle", + "x": 38, + "y": 114, + "width": 16, + "height": 6, + "strokeColor": "transparent", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-home-text", + "type": "text", + "x": 68, + "y": 112, + "width": 50, + "height": 16, + "text": "Home", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users", + "type": "rectangle", + "x": 24, + "y": 156, + "width": 232, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-icon", + "type": "rectangle", + "x": 36, + "y": 166, + "width": 20, + "height": 20, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-icon-head", + "type": "rectangle", + "x": 42, + "y": 168, + "width": 8, + "height": 8, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-icon-body", + "type": "rectangle", + "x": 40, + "y": 178, + "width": 12, + "height": 6, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 3 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-text", + "type": "text", + "x": 68, + "y": 168, + "width": 50, + "height": 16, + "text": "Users", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-orgs", + "type": "rectangle", + "x": 24, + "y": 212, + "width": 232, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-orgs-icon", + "type": "rectangle", + "x": 36, + "y": 222, + "width": 20, + "height": 20, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-orgs-text", + "type": "text", + "x": 68, + "y": 224, + "width": 100, + "height": 16, + "text": "Organizations", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-customers", + "type": "rectangle", + "x": 24, + "y": 268, + "width": 232, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-customers-icon", + "type": "rectangle", + "x": 36, + "y": 278, + "width": 20, + "height": 20, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-customers-text", + "type": "text", + "x": 68, + "y": 280, + "width": 80, + "height": 16, + "text": "Customers", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 24, + "y": 852, + "width": 232, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-avatar", + "type": "rectangle", + "x": 32, + "y": 858, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-avatar-initials", + "type": "text", + "x": 37, + "y": 861, + "width": 10, + "height": 14, + "text": "A", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-name", + "type": "text", + "x": 60, + "y": 860, + "width": 80, + "height": 14, + "text": "Admin", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "text", + "x": 304, + "y": 20, + "width": 160, + "height": 24, + "text": "Admin Dashboard", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-system-status", + "type": "rectangle", + "x": 1140, + "y": 18, + "width": 180, + "height": 28, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 14 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-system-status-dot", + "type": "rectangle", + "x": 1152, + "y": 26, + "width": 12, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-system-status-text", + "type": "text", + "x": 1172, + "y": 25, + "width": 140, + "height": 14, + "text": "All systems operational", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-user-avatar", + "type": "rectangle", + "x": 1388, + "y": 16, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 16 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-shadow", + "type": "rectangle", + "x": 306, + "y": 90, + "width": 260, + "height": 108, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 260, + "height": 108, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-label", + "type": "text", + "x": 328, + "y": 112, + "width": 80, + "height": 16, + "text": "Total Users", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-value", + "type": "text", + "x": 328, + "y": 140, + "width": 100, + "height": 36, + "text": "1,234", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-trend", + "type": "text", + "x": 488, + "y": 148, + "width": 60, + "height": 14, + "text": "+18%", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "$success", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-trend-arrow", + "type": "rectangle", + "x": 534, + "y": 142, + "width": 10, + "height": 6, + "strokeColor": "transparent", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-shadow", + "type": "rectangle", + "x": 582, + "y": 90, + "width": 260, + "height": 108, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2", + "type": "rectangle", + "x": 580, + "y": 88, + "width": 260, + "height": 108, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-label", + "type": "text", + "x": 604, + "y": 112, + "width": 100, + "height": 16, + "text": "Organizations", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-value", + "type": "text", + "x": 604, + "y": 140, + "width": 60, + "height": 36, + "text": "56", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-trend", + "type": "text", + "x": 768, + "y": 148, + "width": 56, + "height": 14, + "text": "+6%", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "$success", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-trend-arrow", + "type": "rectangle", + "x": 810, + "y": 142, + "width": 10, + "height": 6, + "strokeColor": "transparent", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-shadow", + "type": "rectangle", + "x": 858, + "y": 90, + "width": 260, + "height": 108, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3", + "type": "rectangle", + "x": 856, + "y": 88, + "width": 260, + "height": 108, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-label", + "type": "text", + "x": 880, + "y": 112, + "width": 100, + "height": 16, + "text": "Monthly Revenue", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-value", + "type": "text", + "x": 880, + "y": 140, + "width": 120, + "height": 36, + "text": "$12,400", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$success", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-trend", + "type": "text", + "x": 1040, + "y": 148, + "width": 60, + "height": 14, + "text": "+32%", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "$success", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-trend-arrow", + "type": "rectangle", + "x": 1086, + "y": 142, + "width": 10, + "height": 6, + "strokeColor": "transparent", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-shadow", + "type": "rectangle", + "x": 1134, + "y": 90, + "width": 260, + "height": 108, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4", + "type": "rectangle", + "x": 1132, + "y": 88, + "width": 260, + "height": 108, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-label", + "type": "text", + "x": 1156, + "y": 112, + "width": 100, + "height": 16, + "text": "Active Sessions", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-value", + "type": "text", + "x": 1156, + "y": 140, + "width": 80, + "height": 36, + "text": "342", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-trend", + "type": "text", + "x": 1322, + "y": 148, + "width": 56, + "height": 14, + "text": "-3%", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-trend-arrow", + "type": "rectangle", + "x": 1362, + "y": 150, + "width": 10, + "height": 6, + "strokeColor": "transparent", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-card-shadow", + "type": "rectangle", + "x": 306, + "y": 222, + "width": 700, + "height": 460, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-card", + "type": "rectangle", + "x": 304, + "y": 220, + "width": 700, + "height": 460, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-card-title", + "type": "text", + "x": 328, + "y": 244, + "width": 140, + "height": 22, + "text": "Recent Activity", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-card-view-all", + "type": "text", + "x": 920, + "y": 248, + "width": 60, + "height": 14, + "text": "View all", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-1", + "type": "rectangle", + "x": 328, + "y": 284, + "width": 652, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-1-avatar", + "type": "rectangle", + "x": 344, + "y": 300, + "width": 24, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-1-avatar-initials", + "type": "text", + "x": 351, + "y": 305, + "width": 10, + "height": 14, + "text": "J", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-1-icon", + "type": "rectangle", + "x": 380, + "y": 302, + "width": 20, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 30, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-1-text", + "type": "text", + "x": 412, + "y": 300, + "width": 300, + "height": 16, + "text": "User signed up - john@example.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-1-time", + "type": "text", + "x": 912, + "y": 304, + "width": 60, + "height": 14, + "text": "2m ago", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-2", + "type": "rectangle", + "x": 328, + "y": 356, + "width": 652, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-2-avatar", + "type": "rectangle", + "x": 344, + "y": 372, + "width": 24, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-2-avatar-initials", + "type": "text", + "x": 351, + "y": 377, + "width": 10, + "height": 14, + "text": "A", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-2-icon", + "type": "rectangle", + "x": 380, + "y": 374, + "width": 20, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 30, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-2-text", + "type": "text", + "x": 412, + "y": 372, + "width": 280, + "height": 16, + "text": "New org created - Acme Corp", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-2-time", + "type": "text", + "x": 912, + "y": 376, + "width": 60, + "height": 14, + "text": "5m ago", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-3", + "type": "rectangle", + "x": 328, + "y": 428, + "width": 652, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-3-avatar", + "type": "rectangle", + "x": 344, + "y": 444, + "width": 24, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-3-avatar-initials", + "type": "text", + "x": 352, + "y": 449, + "width": 8, + "height": 14, + "text": "$", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-3-icon", + "type": "rectangle", + "x": 380, + "y": 446, + "width": 20, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 30, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-3-text", + "type": "text", + "x": 412, + "y": 444, + "width": 320, + "height": 16, + "text": "Subscription upgraded - Pro Plan ($99/mo)", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-3-time", + "type": "text", + "x": 912, + "y": 448, + "width": 60, + "height": 14, + "text": "12m ago", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-4", + "type": "rectangle", + "x": 328, + "y": 500, + "width": 652, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-4-avatar", + "type": "rectangle", + "x": 344, + "y": 516, + "width": 24, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-4-avatar-initials", + "type": "text", + "x": 351, + "y": 521, + "width": 10, + "height": 14, + "text": "T", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-4-icon", + "type": "rectangle", + "x": 380, + "y": 518, + "width": 20, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 50, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-4-text", + "type": "text", + "x": 412, + "y": 516, + "width": 260, + "height": 16, + "text": "User invited - team@acme.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-4-time", + "type": "text", + "x": 912, + "y": 520, + "width": 60, + "height": 14, + "text": "25m ago", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-5", + "type": "rectangle", + "x": 328, + "y": 572, + "width": 652, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-5-avatar", + "type": "rectangle", + "x": 344, + "y": 588, + "width": 24, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-5-avatar-initials", + "type": "text", + "x": 352, + "y": 593, + "width": 8, + "height": 14, + "text": "!", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-5-icon", + "type": "rectangle", + "x": 380, + "y": 590, + "width": 20, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 30, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-5-text", + "type": "text", + "x": 412, + "y": 588, + "width": 340, + "height": 16, + "text": "API rate limit exceeded - user_123", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-5-time", + "type": "text", + "x": 912, + "y": 592, + "width": 60, + "height": 14, + "text": "1h ago", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-actions-card-shadow", + "type": "rectangle", + "x": 1026, + "y": 222, + "width": 368, + "height": 460, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-actions-card", + "type": "rectangle", + "x": 1024, + "y": 220, + "width": 368, + "height": 460, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-actions-title", + "type": "text", + "x": 1048, + "y": 244, + "width": 120, + "height": 22, + "text": "Quick Actions", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-1", + "type": "rectangle", + "x": 1048, + "y": 284, + "width": 320, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-1-icon", + "type": "rectangle", + "x": 1068, + "y": 298, + "width": 20, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 30, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-1-text", + "type": "text", + "x": 1100, + "y": 300, + "width": 84, + "height": 16, + "text": "Add User", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-2", + "type": "rectangle", + "x": 1048, + "y": 348, + "width": 320, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-2-icon", + "type": "rectangle", + "x": 1068, + "y": 362, + "width": 20, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 20, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-2-text", + "type": "text", + "x": 1100, + "y": 364, + "width": 108, + "height": 16, + "text": "View Reports", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-3", + "type": "rectangle", + "x": 1048, + "y": 412, + "width": 320, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-3-icon", + "type": "rectangle", + "x": 1068, + "y": 426, + "width": 20, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 30, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-3-text", + "type": "text", + "x": 1100, + "y": 428, + "width": 76, + "height": 16, + "text": "Settings", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-4", + "type": "rectangle", + "x": 1048, + "y": 476, + "width": 320, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-4-icon", + "type": "rectangle", + "x": 1068, + "y": 490, + "width": 20, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 20, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-4-text", + "type": "text", + "x": 1100, + "y": 492, + "width": 100, + "height": 16, + "text": "Audit Logs", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-5", + "type": "rectangle", + "x": 1048, + "y": 540, + "width": 320, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-5-icon", + "type": "rectangle", + "x": 1068, + "y": 554, + "width": 20, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 30, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-5-text", + "type": "text", + "x": 1100, + "y": 556, + "width": 140, + "height": 16, + "text": "System Maintenance", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/high-fidelity/dashboard-org.excalidraw b/.context/turbostarter-framework-context/wireframes/high-fidelity/dashboard-org.excalidraw new file mode 100644 index 0000000..13c4d71 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/high-fidelity/dashboard-org.excalidraw @@ -0,0 +1,2306 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 24, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-text", + "type": "text", + "x": 36, + "y": 24, + "width": 96, + "height": 18, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-overview", + "type": "rectangle", + "x": 24, + "y": 100, + "width": 232, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-overview-icon", + "type": "rectangle", + "x": 36, + "y": 110, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-overview-text", + "type": "text", + "x": 68, + "y": 112, + "width": 80, + "height": 16, + "text": "Overview", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members", + "type": "rectangle", + "x": 24, + "y": 156, + "width": 232, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-icon", + "type": "rectangle", + "x": 36, + "y": 166, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-text", + "type": "text", + "x": 68, + "y": 168, + "width": 70, + "height": 16, + "text": "Members", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings", + "type": "rectangle", + "x": 24, + "y": 212, + "width": 232, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-icon", + "type": "rectangle", + "x": 36, + "y": 222, + "width": 20, + "height": 20, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-text", + "type": "text", + "x": 68, + "y": 224, + "width": 60, + "height": 16, + "text": "Settings", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing", + "type": "rectangle", + "x": 24, + "y": 268, + "width": 232, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing-icon", + "type": "rectangle", + "x": 36, + "y": 278, + "width": 20, + "height": 20, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing-text", + "type": "text", + "x": 68, + "y": 280, + "width": 50, + "height": 16, + "text": "Billing", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 24, + "y": 852, + "width": 232, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-avatar", + "type": "rectangle", + "x": 32, + "y": 858, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-avatar-initials", + "type": "text", + "x": 37, + "y": 861, + "width": 10, + "height": 14, + "text": "A", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-name", + "type": "text", + "x": 60, + "y": 860, + "width": 80, + "height": 14, + "text": "Acme Inc.", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "text", + "x": 304, + "y": 20, + "width": 200, + "height": 24, + "text": "Organization Overview", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-time-selector", + "type": "rectangle", + "x": 1240, + "y": 16, + "width": 128, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-time-selector-text", + "type": "text", + "x": 1256, + "y": 24, + "width": 80, + "height": 16, + "text": "Last 7 days", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-time-selector-arrow", + "type": "rectangle", + "x": 1348, + "y": 24, + "width": 12, + "height": 6, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-user-avatar", + "type": "rectangle", + "x": 1388, + "y": 16, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 16 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-shadow", + "type": "rectangle", + "x": 306, + "y": 90, + "width": 260, + "height": 108, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 260, + "height": 108, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-label", + "type": "text", + "x": 328, + "y": 112, + "width": 100, + "height": 16, + "text": "Total Members", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-value", + "type": "text", + "x": 328, + "y": 140, + "width": 80, + "height": 36, + "text": "24", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-trend", + "type": "text", + "x": 488, + "y": 148, + "width": 60, + "height": 14, + "text": "+12%", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "$success", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-trend-arrow", + "type": "rectangle", + "x": 534, + "y": 142, + "width": 10, + "height": 6, + "strokeColor": "transparent", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-shadow", + "type": "rectangle", + "x": 582, + "y": 90, + "width": 260, + "height": 108, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2", + "type": "rectangle", + "x": 580, + "y": 88, + "width": 260, + "height": 108, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-label", + "type": "text", + "x": 604, + "y": 112, + "width": 100, + "height": 16, + "text": "Active Users", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-value", + "type": "text", + "x": 604, + "y": 140, + "width": 60, + "height": 36, + "text": "18", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-trend", + "type": "text", + "x": 768, + "y": 148, + "width": 56, + "height": 14, + "text": "+8%", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "$success", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-trend-arrow", + "type": "rectangle", + "x": 810, + "y": 142, + "width": 10, + "height": 6, + "strokeColor": "transparent", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-shadow", + "type": "rectangle", + "x": 858, + "y": 90, + "width": 260, + "height": 108, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3", + "type": "rectangle", + "x": 856, + "y": 88, + "width": 260, + "height": 108, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-label", + "type": "text", + "x": 880, + "y": 112, + "width": 80, + "height": 16, + "text": "API Calls", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-value", + "type": "text", + "x": 880, + "y": 140, + "width": 100, + "height": 36, + "text": "12,500", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-trend", + "type": "text", + "x": 1040, + "y": 148, + "width": 60, + "height": 14, + "text": "+24%", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "$success", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-trend-arrow", + "type": "rectangle", + "x": 1086, + "y": 142, + "width": 10, + "height": 6, + "strokeColor": "transparent", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-shadow", + "type": "rectangle", + "x": 1134, + "y": 90, + "width": 260, + "height": 108, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4", + "type": "rectangle", + "x": 1132, + "y": 88, + "width": 260, + "height": 108, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-label", + "type": "text", + "x": 1156, + "y": 112, + "width": 100, + "height": 16, + "text": "Monthly Cost", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-value", + "type": "text", + "x": 1156, + "y": 140, + "width": 100, + "height": 36, + "text": "$2,400", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-trend", + "type": "text", + "x": 1322, + "y": 148, + "width": 56, + "height": 14, + "text": "-5%", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-trend-arrow", + "type": "rectangle", + "x": 1362, + "y": 150, + "width": 10, + "height": 6, + "strokeColor": "transparent", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-card-shadow", + "type": "rectangle", + "x": 306, + "y": 222, + "width": 1086, + "height": 320, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-card", + "type": "rectangle", + "x": 304, + "y": 220, + "width": 1086, + "height": 320, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-title", + "type": "text", + "x": 328, + "y": 244, + "width": 140, + "height": 22, + "text": "Usage Over Time", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-area", + "type": "rectangle", + "x": 328, + "y": 284, + "width": 1038, + "height": 232, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-y-axis-5k", + "type": "text", + "x": 340, + "y": 296, + "width": 30, + "height": 12, + "text": "5k", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-y-axis-4k", + "type": "text", + "x": 340, + "y": 346, + "width": 30, + "height": 12, + "text": "4k", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-y-axis-3k", + "type": "text", + "x": 340, + "y": 396, + "width": 30, + "height": 12, + "text": "3k", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-y-axis-2k", + "type": "text", + "x": 340, + "y": 446, + "width": 30, + "height": 12, + "text": "2k", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-grid-1", + "type": "rectangle", + "x": 378, + "y": 300, + "width": 976, + "height": 1, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 20, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-grid-2", + "type": "rectangle", + "x": 378, + "y": 350, + "width": 976, + "height": 1, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 20, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-grid-3", + "type": "rectangle", + "x": 378, + "y": 400, + "width": 976, + "height": 1, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 20, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-grid-4", + "type": "rectangle", + "x": 378, + "y": 450, + "width": 976, + "height": 1, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 20, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-area-fill", + "type": "rectangle", + "x": 378, + "y": 350, + "width": 976, + "height": 140, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 15, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-line", + "type": "rectangle", + "x": 378, + "y": 350, + "width": 976, + "height": 3, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-point-1", + "type": "rectangle", + "x": 438, + "y": 376, + "width": 8, + "height": 8, + "strokeColor": "$card", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-point-2", + "type": "rectangle", + "x": 598, + "y": 346, + "width": 8, + "height": 8, + "strokeColor": "$card", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-point-3", + "type": "rectangle", + "x": 758, + "y": 330, + "width": 8, + "height": 8, + "strokeColor": "$card", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-point-4", + "type": "rectangle", + "x": 918, + "y": 360, + "width": 8, + "height": 8, + "strokeColor": "$card", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-point-5", + "type": "rectangle", + "x": 1078, + "y": 320, + "width": 8, + "height": 8, + "strokeColor": "$card", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-point-6", + "type": "rectangle", + "x": 1238, + "y": 310, + "width": 8, + "height": 8, + "strokeColor": "$card", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-point-7", + "type": "rectangle", + "x": 1338, + "y": 298, + "width": 8, + "height": 8, + "strokeColor": "$card", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-axis-mon", + "type": "text", + "x": 428, + "y": 498, + "width": 30, + "height": 12, + "text": "Mon", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-axis-tue", + "type": "text", + "x": 588, + "y": 498, + "width": 30, + "height": 12, + "text": "Tue", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-axis-wed", + "type": "text", + "x": 748, + "y": 498, + "width": 30, + "height": 12, + "text": "Wed", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-axis-thu", + "type": "text", + "x": 908, + "y": 498, + "width": 30, + "height": 12, + "text": "Thu", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-axis-fri", + "type": "text", + "x": 1068, + "y": 498, + "width": 30, + "height": 12, + "text": "Fri", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-axis-sat", + "type": "text", + "x": 1228, + "y": 498, + "width": 30, + "height": 12, + "text": "Sat", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-axis-sun", + "type": "text", + "x": 1328, + "y": 498, + "width": 30, + "height": 12, + "text": "Sun", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-shadow", + "type": "rectangle", + "x": 306, + "y": 566, + "width": 532, + "height": 280, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1", + "type": "rectangle", + "x": 304, + "y": 564, + "width": 532, + "height": 280, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-title", + "type": "text", + "x": 328, + "y": 588, + "width": 120, + "height": 22, + "text": "Usage by Tool", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-area", + "type": "rectangle", + "x": 328, + "y": 624, + "width": 484, + "height": 196, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-1", + "type": "rectangle", + "x": 368, + "y": 716, + "width": 48, + "height": 80, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-2", + "type": "rectangle", + "x": 440, + "y": 668, + "width": 48, + "height": 128, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-3", + "type": "rectangle", + "x": 512, + "y": 744, + "width": 48, + "height": 52, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-4", + "type": "rectangle", + "x": 584, + "y": 648, + "width": 48, + "height": 148, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-5", + "type": "rectangle", + "x": 656, + "y": 696, + "width": 48, + "height": 100, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-6", + "type": "rectangle", + "x": 728, + "y": 726, + "width": 48, + "height": 70, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-shadow", + "type": "rectangle", + "x": 856, + "y": 566, + "width": 532, + "height": 280, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2", + "type": "rectangle", + "x": 854, + "y": 564, + "width": 532, + "height": 280, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-title", + "type": "text", + "x": 878, + "y": 588, + "width": 120, + "height": 22, + "text": "Distribution", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-donut-outer", + "type": "rectangle", + "x": 948, + "y": 644, + "width": 160, + "height": 160, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 80 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-donut-seg2", + "type": "rectangle", + "x": 948, + "y": 724, + "width": 80, + "height": 80, + "strokeColor": "transparent", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-donut-seg3", + "type": "rectangle", + "x": 1028, + "y": 764, + "width": 80, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-donut-inner", + "type": "rectangle", + "x": 988, + "y": 684, + "width": 80, + "height": 80, + "strokeColor": "transparent", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 40 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-donut-center-value", + "type": "text", + "x": 1004, + "y": 712, + "width": 48, + "height": 24, + "text": "100%", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-legend-1-box", + "type": "rectangle", + "x": 1148, + "y": 672, + "width": 16, + "height": 16, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-legend-1-text", + "type": "text", + "x": 1172, + "y": 674, + "width": 80, + "height": 14, + "text": "Chat 45%", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-legend-2-box", + "type": "rectangle", + "x": 1148, + "y": 704, + "width": 16, + "height": 16, + "strokeColor": "transparent", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-legend-2-text", + "type": "text", + "x": 1172, + "y": 706, + "width": 80, + "height": 14, + "text": "Image 35%", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-legend-3-box", + "type": "rectangle", + "x": 1148, + "y": 736, + "width": 16, + "height": 16, + "strokeColor": "transparent", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-legend-3-text", + "type": "text", + "x": 1172, + "y": 738, + "width": 80, + "height": 14, + "text": "PDF 20%", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/high-fidelity/dashboard-user.excalidraw b/.context/turbostarter-framework-context/wireframes/high-fidelity/dashboard-user.excalidraw new file mode 100644 index 0000000..277d52a --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/high-fidelity/dashboard-user.excalidraw @@ -0,0 +1,1641 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 24, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-text", + "type": "text", + "x": 36, + "y": 24, + "width": 96, + "height": 18, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard", + "type": "rectangle", + "x": 24, + "y": 100, + "width": 232, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-icon", + "type": "rectangle", + "x": 36, + "y": 110, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-icon-grid", + "type": "rectangle", + "x": 40, + "y": 114, + "width": 5, + "height": 5, + "strokeColor": "transparent", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 1 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-icon-grid-2", + "type": "rectangle", + "x": 47, + "y": 114, + "width": 5, + "height": 5, + "strokeColor": "transparent", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 1 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-icon-grid-3", + "type": "rectangle", + "x": 40, + "y": 121, + "width": 5, + "height": 5, + "strokeColor": "transparent", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 1 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-icon-grid-4", + "type": "rectangle", + "x": 47, + "y": 121, + "width": 5, + "height": 5, + "strokeColor": "transparent", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 1 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-text", + "type": "text", + "x": 68, + "y": 112, + "width": 80, + "height": 16, + "text": "Dashboard", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-aitools", + "type": "rectangle", + "x": 24, + "y": 156, + "width": 232, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-aitools-icon", + "type": "rectangle", + "x": 36, + "y": 166, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-aitools-text", + "type": "text", + "x": 68, + "y": 168, + "width": 60, + "height": 16, + "text": "AI Tools", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings", + "type": "rectangle", + "x": 24, + "y": 212, + "width": 232, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-icon", + "type": "rectangle", + "x": 36, + "y": 222, + "width": 20, + "height": 20, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-text", + "type": "text", + "x": 68, + "y": 224, + "width": 60, + "height": 16, + "text": "Settings", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-support-item", + "type": "rectangle", + "x": 24, + "y": 720, + "width": 232, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-support-icon", + "type": "rectangle", + "x": 36, + "y": 730, + "width": 20, + "height": 20, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-support-text", + "type": "text", + "x": 68, + "y": 732, + "width": 60, + "height": 16, + "text": "Support", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-feedback-item", + "type": "rectangle", + "x": 24, + "y": 776, + "width": 232, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-feedback-icon", + "type": "rectangle", + "x": 36, + "y": 786, + "width": 20, + "height": 20, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-feedback-text", + "type": "text", + "x": 68, + "y": 788, + "width": 70, + "height": 16, + "text": "Feedback", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 24, + "y": 852, + "width": 232, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-avatar", + "type": "rectangle", + "x": 32, + "y": 858, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-avatar-initials", + "type": "text", + "x": 37, + "y": 861, + "width": 10, + "height": 14, + "text": "J", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-name", + "type": "text", + "x": 60, + "y": 860, + "width": 80, + "height": 14, + "text": "John Doe", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "text", + "x": 304, + "y": 20, + "width": 120, + "height": 24, + "text": "Dashboard", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-user-avatar", + "type": "rectangle", + "x": 1388, + "y": 16, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 16 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-user-avatar-initials", + "type": "text", + "x": 1397, + "y": 24, + "width": 14, + "height": 16, + "text": "JD", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-card-shadow", + "type": "rectangle", + "x": 306, + "y": 90, + "width": 1112, + "height": 120, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-card", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 1112, + "height": 120, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-title", + "type": "text", + "x": 328, + "y": 112, + "width": 260, + "height": 28, + "text": "Welcome back, John!", + "fontSize": 24, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-subtitle", + "type": "text", + "x": 328, + "y": 148, + "width": 280, + "height": 18, + "text": "Get started with our AI-powered tools", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-subtitle-2", + "type": "text", + "x": 328, + "y": 172, + "width": 360, + "height": 14, + "text": "Explore chat, image generation, and document tools below.", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-shadow", + "type": "rectangle", + "x": 306, + "y": 234, + "width": 352, + "height": 220, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1", + "type": "rectangle", + "x": 304, + "y": 232, + "width": 352, + "height": 220, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-icon-bg", + "type": "rectangle", + "x": 328, + "y": 256, + "width": 56, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-icon-bubble", + "type": "rectangle", + "x": 340, + "y": 268, + "width": 32, + "height": 24, + "strokeColor": "transparent", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-icon-tail", + "type": "rectangle", + "x": 344, + "y": 288, + "width": 8, + "height": 8, + "strokeColor": "transparent", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-title", + "type": "text", + "x": 328, + "y": 328, + "width": 100, + "height": 22, + "text": "AI Chat", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-desc", + "type": "text", + "x": 328, + "y": 358, + "width": 300, + "height": 18, + "text": "Start conversations with AI assistants", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-desc-2", + "type": "text", + "x": 328, + "y": 380, + "width": 280, + "height": 14, + "text": "Ask questions, get help, brainstorm ideas.", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-button", + "type": "rectangle", + "x": 328, + "y": 408, + "width": 100, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-button-text", + "type": "text", + "x": 352, + "y": 418, + "width": 52, + "height": 16, + "text": "Open", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-shadow", + "type": "rectangle", + "x": 678, + "y": 234, + "width": 352, + "height": 220, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2", + "type": "rectangle", + "x": 676, + "y": 232, + "width": 352, + "height": 220, + "strokeColor": "$primary", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-new-badge", + "type": "rectangle", + "x": 968, + "y": 244, + "width": 44, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-new-badge-text", + "type": "text", + "x": 976, + "y": 248, + "width": 28, + "height": 12, + "text": "New", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-icon-bg", + "type": "rectangle", + "x": 700, + "y": 256, + "width": 56, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-icon-frame", + "type": "rectangle", + "x": 712, + "y": 268, + "width": 32, + "height": 32, + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-icon-mountain", + "type": "rectangle", + "x": 718, + "y": 284, + "width": 20, + "height": 10, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 50, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-icon-sun", + "type": "rectangle", + "x": 734, + "y": 274, + "width": 6, + "height": 6, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 50, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 3 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-title", + "type": "text", + "x": 700, + "y": 328, + "width": 160, + "height": 22, + "text": "Image Generation", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-desc", + "type": "text", + "x": 700, + "y": 358, + "width": 280, + "height": 18, + "text": "Create stunning images from text", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-desc-2", + "type": "text", + "x": 700, + "y": 380, + "width": 260, + "height": 14, + "text": "Generate artwork, illustrations, and photos.", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-button", + "type": "rectangle", + "x": 700, + "y": 408, + "width": 100, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-button-text", + "type": "text", + "x": 718, + "y": 418, + "width": 64, + "height": 16, + "text": "Try Now", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-shadow", + "type": "rectangle", + "x": 1050, + "y": 234, + "width": 352, + "height": 220, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3", + "type": "rectangle", + "x": 1048, + "y": 232, + "width": 352, + "height": 220, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-icon-bg", + "type": "rectangle", + "x": 1072, + "y": 256, + "width": 56, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-icon-doc", + "type": "rectangle", + "x": 1086, + "y": 266, + "width": 28, + "height": 36, + "strokeColor": "transparent", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-icon-doc-line1", + "type": "rectangle", + "x": 1090, + "y": 274, + "width": 20, + "height": 3, + "strokeColor": "transparent", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 1 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-icon-doc-line2", + "type": "rectangle", + "x": 1090, + "y": 280, + "width": 16, + "height": 3, + "strokeColor": "transparent", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 1 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-icon-doc-line3", + "type": "rectangle", + "x": 1090, + "y": 286, + "width": 20, + "height": 3, + "strokeColor": "transparent", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 1 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-icon-doc-line4", + "type": "rectangle", + "x": 1090, + "y": 292, + "width": 14, + "height": 3, + "strokeColor": "transparent", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 1 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-title", + "type": "text", + "x": 1072, + "y": 328, + "width": 100, + "height": 22, + "text": "PDF Tools", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-desc", + "type": "text", + "x": 1072, + "y": 358, + "width": 280, + "height": 18, + "text": "Chat with your documents", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-desc-2", + "type": "text", + "x": 1072, + "y": 380, + "width": 300, + "height": 14, + "text": "Upload PDFs and ask questions about them.", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-button", + "type": "rectangle", + "x": 1072, + "y": 408, + "width": 100, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-button-text", + "type": "text", + "x": 1092, + "y": 418, + "width": 60, + "height": 16, + "text": "Upload", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/high-fidelity/data-table-invitations.excalidraw b/.context/turbostarter-framework-context/wireframes/high-fidelity/data-table-invitations.excalidraw new file mode 100644 index 0000000..07cf99b --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/high-fidelity/data-table-invitations.excalidraw @@ -0,0 +1,2755 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-switcher", + "type": "rectangle", + "x": 20, + "y": 80, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-avatar", + "type": "rectangle", + "x": 32, + "y": 88, + "width": 24, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-name", + "type": "text", + "x": 64, + "y": 92, + "width": 100, + "height": 16, + "text": "Acme Inc", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-chevron", + "type": "line", + "x": 240, + "y": 96, + "width": 8, + "height": 5, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [4, 5], [8, 0]] + }, + { + "id": "sidebar-section-label", + "type": "text", + "x": 20, + "y": 140, + "width": 80, + "height": 12, + "text": "WORKSPACE", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-icon", + "type": "rectangle", + "x": 32, + "y": 170, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-label", + "type": "text", + "x": 60, + "y": 172, + "width": 80, + "height": 16, + "text": "Dashboard", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-active", + "type": "rectangle", + "x": 20, + "y": 208, + "width": 240, + "height": 40, + "strokeColor": "$primary", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-icon", + "type": "ellipse", + "x": 32, + "y": 218, + "width": 20, + "height": 20, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-label", + "type": "text", + "x": 60, + "y": 220, + "width": 60, + "height": 16, + "text": "Members", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings", + "type": "rectangle", + "x": 20, + "y": 256, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-icon", + "type": "rectangle", + "x": 32, + "y": 266, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-label", + "type": "text", + "x": 60, + "y": 268, + "width": 60, + "height": 16, + "text": "Settings", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing", + "type": "rectangle", + "x": 20, + "y": 304, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing-icon", + "type": "rectangle", + "x": 32, + "y": 314, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing-label", + "type": "text", + "x": 60, + "y": 316, + "width": 50, + "height": 16, + "text": "Billing", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-avatar", + "type": "ellipse", + "x": 28, + "y": 856, + "width": 24, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-name", + "type": "text", + "x": 60, + "y": 860, + "width": 80, + "height": 16, + "text": "John Doe", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "text", + "x": 304, + "y": 20, + "width": 80, + "height": 24, + "text": "Members", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invite-button", + "type": "rectangle", + "x": 1260, + "y": 16, + "width": 156, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invite-icon", + "type": "line", + "x": 1276, + "y": 26, + "width": 12, + "height": 12, + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[6, 0], [6, 12]] + }, + { + "id": "header-invite-icon2", + "type": "line", + "x": 1276, + "y": 26, + "width": 12, + "height": 12, + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 6], [12, 6]] + }, + { + "id": "header-invite-text", + "type": "text", + "x": 1296, + "y": 24, + "width": 100, + "height": 16, + "text": "Invite Member", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tabs-container", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 1112, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-members", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 120, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-members-label", + "type": "text", + "x": 316, + "y": 104, + "width": 96, + "height": 16, + "text": "Members (24)", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["tabs-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-invitations-active", + "type": "rectangle", + "x": 424, + "y": 88, + "width": 180, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-invitations-label", + "type": "text", + "x": 436, + "y": 104, + "width": 156, + "height": 16, + "text": "Pending Invitations (3)", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-invitations-underline", + "type": "rectangle", + "x": 424, + "y": 132, + "width": 180, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-container", + "type": "rectangle", + "x": 304, + "y": 152, + "width": 1112, + "height": 716, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "toolbar", + "type": "rectangle", + "x": 304, + "y": 152, + "width": 1112, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-input", + "type": "rectangle", + "x": 316, + "y": 162, + "width": 260, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-icon", + "type": "ellipse", + "x": 326, + "y": 170, + "width": 16, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-icon-handle", + "type": "line", + "x": 339, + "y": 183, + "width": 6, + "height": 6, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [6, 6]] + }, + { + "id": "search-placeholder", + "type": "text", + "x": 350, + "y": 172, + "width": 160, + "height": 16, + "text": "Search invitations...", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-status", + "type": "rectangle", + "x": 588, + "y": 162, + "width": 100, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-status-text", + "type": "text", + "x": 600, + "y": 172, + "width": 50, + "height": 16, + "text": "Status", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-status-chevron", + "type": "line", + "x": 668, + "y": 176, + "width": 8, + "height": 5, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [4, 5], [8, 0]] + }, + { + "id": "filter-role", + "type": "rectangle", + "x": 700, + "y": 162, + "width": 100, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role-text", + "type": "text", + "x": 712, + "y": 172, + "width": 40, + "height": 16, + "text": "Role", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role-chevron", + "type": "line", + "x": 780, + "y": 176, + "width": 8, + "height": 5, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [4, 5], [8, 0]] + }, + { + "id": "table-header-row", + "type": "rectangle", + "x": 304, + "y": 208, + "width": 1112, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-email-col", + "type": "text", + "x": 320, + "y": 224, + "width": 50, + "height": 14, + "text": "Email", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-role-col", + "type": "text", + "x": 480, + "y": 224, + "width": 40, + "height": 14, + "text": "Role", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invitedby-col", + "type": "text", + "x": 600, + "y": 224, + "width": 70, + "height": 14, + "text": "Invited by", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-sent-col", + "type": "text", + "x": 760, + "y": 224, + "width": 40, + "height": 14, + "text": "Sent", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-expires-col", + "type": "text", + "x": 880, + "y": 224, + "width": 55, + "height": 14, + "text": "Expires", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-status-col", + "type": "text", + "x": 1000, + "y": 224, + "width": 50, + "height": 14, + "text": "Status", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-actions-col", + "type": "text", + "x": 1160, + "y": 224, + "width": 55, + "height": 14, + "text": "Actions", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-1", + "type": "rectangle", + "x": 304, + "y": 256, + "width": 1112, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-email", + "type": "text", + "x": 320, + "y": 277, + "width": 140, + "height": 14, + "text": "alice@example.com", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-badge", + "type": "rectangle", + "x": 480, + "y": 274, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-text", + "type": "text", + "x": 490, + "y": 277, + "width": 45, + "height": 14, + "text": "Member", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-invitedby-avatar", + "type": "ellipse", + "x": 600, + "y": 272, + "width": 24, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-invitedby-initials", + "type": "text", + "x": 606, + "y": 278, + "width": 12, + "height": 12, + "text": "JD", + "fontSize": 9, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-invitedby", + "type": "text", + "x": 632, + "y": 277, + "width": 80, + "height": 14, + "text": "John Doe", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-sent", + "type": "text", + "x": 760, + "y": 277, + "width": 75, + "height": 14, + "text": "Jan 20, 2024", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-expires", + "type": "text", + "x": 880, + "y": 277, + "width": 75, + "height": 14, + "text": "Jan 27, 2024", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status-badge", + "type": "rectangle", + "x": 1000, + "y": 273, + "width": 66, + "height": 22, + "strokeColor": "transparent", + "backgroundColor": "#facc15", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 25, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 11 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status-dot", + "type": "ellipse", + "x": 1010, + "y": 280, + "width": 8, + "height": 8, + "strokeColor": "transparent", + "backgroundColor": "#facc15", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status-text", + "type": "text", + "x": 1022, + "y": 277, + "width": 40, + "height": 14, + "text": "Pending", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ca8a04", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-resend", + "type": "rectangle", + "x": 1160, + "y": 272, + "width": 70, + "height": 26, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-resend-icon", + "type": "ellipse", + "x": 1168, + "y": 280, + "width": 12, + "height": 12, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-resend-text", + "type": "text", + "x": 1184, + "y": 278, + "width": 40, + "height": 14, + "text": "Resend", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-cancel", + "type": "rectangle", + "x": 1240, + "y": 272, + "width": 70, + "height": 26, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-cancel-icon-x1", + "type": "line", + "x": 1250, + "y": 280, + "width": 8, + "height": 8, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [8, 8]] + }, + { + "id": "row-1-action-cancel-icon-x2", + "type": "line", + "x": 1250, + "y": 288, + "width": 8, + "height": 8, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [8, -8]] + }, + { + "id": "row-1-action-cancel-text", + "type": "text", + "x": 1264, + "y": 278, + "width": 40, + "height": 14, + "text": "Cancel", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-2-hover", + "type": "rectangle", + "x": 304, + "y": 312, + "width": 1112, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 40, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-email", + "type": "text", + "x": 320, + "y": 333, + "width": 150, + "height": 14, + "text": "charlie@example.com", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-badge", + "type": "rectangle", + "x": 480, + "y": 330, + "width": 55, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 25, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-text", + "type": "text", + "x": 492, + "y": 333, + "width": 35, + "height": 14, + "text": "Admin", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-invitedby-avatar", + "type": "ellipse", + "x": 600, + "y": 328, + "width": 24, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-invitedby-initials", + "type": "text", + "x": 607, + "y": 334, + "width": 10, + "height": 12, + "text": "JS", + "fontSize": 9, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-invitedby", + "type": "text", + "x": 632, + "y": 333, + "width": 80, + "height": 14, + "text": "Jane Smith", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-sent", + "type": "text", + "x": 760, + "y": 333, + "width": 75, + "height": 14, + "text": "Jan 15, 2024", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-expires", + "type": "text", + "x": 880, + "y": 333, + "width": 75, + "height": 14, + "text": "Jan 22, 2024", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status-badge", + "type": "rectangle", + "x": 1000, + "y": 329, + "width": 62, + "height": 22, + "strokeColor": "transparent", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 25, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 11 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status-dot", + "type": "ellipse", + "x": 1010, + "y": 336, + "width": 8, + "height": 8, + "strokeColor": "transparent", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status-text", + "type": "text", + "x": 1022, + "y": 333, + "width": 38, + "height": 14, + "text": "Expired", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-resend", + "type": "rectangle", + "x": 1160, + "y": 328, + "width": 70, + "height": 26, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-resend-icon", + "type": "ellipse", + "x": 1168, + "y": 336, + "width": 12, + "height": 12, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-resend-text", + "type": "text", + "x": 1184, + "y": 334, + "width": 40, + "height": 14, + "text": "Resend", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-cancel", + "type": "rectangle", + "x": 1240, + "y": 328, + "width": 70, + "height": 26, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-cancel-icon-x1", + "type": "line", + "x": 1250, + "y": 336, + "width": 8, + "height": 8, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [8, 8]] + }, + { + "id": "row-2-action-cancel-icon-x2", + "type": "line", + "x": 1250, + "y": 344, + "width": 8, + "height": 8, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [8, -8]] + }, + { + "id": "row-2-action-cancel-text", + "type": "text", + "x": 1264, + "y": 334, + "width": 40, + "height": 14, + "text": "Cancel", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-3", + "type": "rectangle", + "x": 304, + "y": 368, + "width": 1112, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-email", + "type": "text", + "x": 320, + "y": 389, + "width": 130, + "height": 14, + "text": "dave@example.com", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-badge", + "type": "rectangle", + "x": 480, + "y": 386, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-text", + "type": "text", + "x": 490, + "y": 389, + "width": 45, + "height": 14, + "text": "Member", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-invitedby-avatar", + "type": "ellipse", + "x": 600, + "y": 384, + "width": 24, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-invitedby-initials", + "type": "text", + "x": 606, + "y": 390, + "width": 12, + "height": 12, + "text": "JD", + "fontSize": 9, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-invitedby", + "type": "text", + "x": 632, + "y": 389, + "width": 80, + "height": 14, + "text": "John Doe", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-sent", + "type": "text", + "x": 760, + "y": 389, + "width": 75, + "height": 14, + "text": "Jan 25, 2024", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-expires", + "type": "text", + "x": 880, + "y": 389, + "width": 75, + "height": 14, + "text": "Feb 1, 2024", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-status-badge", + "type": "rectangle", + "x": 1000, + "y": 385, + "width": 66, + "height": 22, + "strokeColor": "transparent", + "backgroundColor": "#facc15", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 25, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 11 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-status-dot", + "type": "ellipse", + "x": 1010, + "y": 392, + "width": 8, + "height": 8, + "strokeColor": "transparent", + "backgroundColor": "#facc15", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-status-text", + "type": "text", + "x": 1022, + "y": 389, + "width": 40, + "height": 14, + "text": "Pending", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ca8a04", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-resend", + "type": "rectangle", + "x": 1160, + "y": 384, + "width": 70, + "height": 26, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-resend-icon", + "type": "ellipse", + "x": 1168, + "y": 392, + "width": 12, + "height": 12, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-resend-text", + "type": "text", + "x": 1184, + "y": 390, + "width": 40, + "height": 14, + "text": "Resend", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-cancel", + "type": "rectangle", + "x": 1240, + "y": 384, + "width": 70, + "height": 26, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-cancel-icon-x1", + "type": "line", + "x": 1250, + "y": 392, + "width": 8, + "height": 8, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [8, 8]] + }, + { + "id": "row-3-action-cancel-icon-x2", + "type": "line", + "x": 1250, + "y": 400, + "width": 8, + "height": 8, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [8, -8]] + }, + { + "id": "row-3-action-cancel-text", + "type": "text", + "x": 1264, + "y": 390, + "width": 40, + "height": 14, + "text": "Cancel", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "empty-rows-area", + "type": "rectangle", + "x": 304, + "y": 424, + "width": 1112, + "height": 396, + "strokeColor": "transparent", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-bar", + "type": "rectangle", + "x": 304, + "y": 820, + "width": 1112, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-info", + "type": "text", + "x": 328, + "y": 836, + "width": 110, + "height": 16, + "text": "Showing 1-3 of 3", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-prev", + "type": "rectangle", + "x": 1284, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-prev-icon", + "type": "line", + "x": 1297, + "y": 838, + "width": 6, + "height": 10, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 50, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[6, 0], [0, 5], [6, 10]] + }, + { + "id": "page-1", + "type": "rectangle", + "x": 1324, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-1-text", + "type": "text", + "x": 1336, + "y": 838, + "width": 10, + "height": 12, + "text": "1", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-next", + "type": "rectangle", + "x": 1364, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-next-icon", + "type": "line", + "x": 1374, + "y": 838, + "width": 6, + "height": 10, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 50, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [6, 5], [0, 10]] + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/high-fidelity/data-table-members.excalidraw b/.context/turbostarter-framework-context/wireframes/high-fidelity/data-table-members.excalidraw new file mode 100644 index 0000000..a532f47 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/high-fidelity/data-table-members.excalidraw @@ -0,0 +1,2510 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-switcher", + "type": "rectangle", + "x": 20, + "y": 80, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-avatar", + "type": "rectangle", + "x": 32, + "y": 88, + "width": 24, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-name", + "type": "text", + "x": 64, + "y": 92, + "width": 100, + "height": 16, + "text": "Acme Inc", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-chevron", + "type": "line", + "x": 240, + "y": 96, + "width": 8, + "height": 5, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [4, 5], [8, 0]] + }, + { + "id": "sidebar-section-label", + "type": "text", + "x": 20, + "y": 140, + "width": 80, + "height": 12, + "text": "WORKSPACE", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-icon", + "type": "rectangle", + "x": 32, + "y": 170, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-label", + "type": "text", + "x": 60, + "y": 172, + "width": 80, + "height": 16, + "text": "Dashboard", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-active", + "type": "rectangle", + "x": 20, + "y": 208, + "width": 240, + "height": 40, + "strokeColor": "$primary", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-icon", + "type": "ellipse", + "x": 32, + "y": 218, + "width": 20, + "height": 20, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-label", + "type": "text", + "x": 60, + "y": 220, + "width": 60, + "height": 16, + "text": "Members", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings", + "type": "rectangle", + "x": 20, + "y": 256, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-icon", + "type": "rectangle", + "x": 32, + "y": 266, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-label", + "type": "text", + "x": 60, + "y": 268, + "width": 60, + "height": 16, + "text": "Settings", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing", + "type": "rectangle", + "x": 20, + "y": 304, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing-icon", + "type": "rectangle", + "x": 32, + "y": 314, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing-label", + "type": "text", + "x": 60, + "y": 316, + "width": 50, + "height": 16, + "text": "Billing", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-avatar", + "type": "ellipse", + "x": 28, + "y": 856, + "width": 24, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-name", + "type": "text", + "x": 60, + "y": 860, + "width": 80, + "height": 16, + "text": "John Doe", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "text", + "x": 304, + "y": 20, + "width": 80, + "height": 24, + "text": "Members", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invite-button", + "type": "rectangle", + "x": 1260, + "y": 16, + "width": 156, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invite-icon", + "type": "line", + "x": 1276, + "y": 26, + "width": 12, + "height": 12, + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[6, 0], [6, 12]] + }, + { + "id": "header-invite-icon2", + "type": "line", + "x": 1276, + "y": 26, + "width": 12, + "height": 12, + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 6], [12, 6]] + }, + { + "id": "header-invite-text", + "type": "text", + "x": 1296, + "y": 24, + "width": 100, + "height": 16, + "text": "Invite Member", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tabs-container", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 1112, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-members-active", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 120, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-members-label", + "type": "text", + "x": 316, + "y": 104, + "width": 96, + "height": 16, + "text": "Members (24)", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-members-underline", + "type": "rectangle", + "x": 304, + "y": 132, + "width": 120, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-invitations", + "type": "rectangle", + "x": 424, + "y": 88, + "width": 180, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-invitations-label", + "type": "text", + "x": 436, + "y": 104, + "width": 156, + "height": 16, + "text": "Pending Invitations (3)", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["tabs-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-container", + "type": "rectangle", + "x": 304, + "y": 152, + "width": 1112, + "height": 716, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "toolbar", + "type": "rectangle", + "x": 304, + "y": 152, + "width": 1112, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-input", + "type": "rectangle", + "x": 316, + "y": 162, + "width": 260, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-icon", + "type": "ellipse", + "x": 326, + "y": 170, + "width": 16, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-icon-handle", + "type": "line", + "x": 339, + "y": 183, + "width": 6, + "height": 6, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [6, 6]] + }, + { + "id": "search-placeholder", + "type": "text", + "x": 350, + "y": 172, + "width": 140, + "height": 16, + "text": "Search members...", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role", + "type": "rectangle", + "x": 588, + "y": 162, + "width": 100, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role-text", + "type": "text", + "x": 600, + "y": 172, + "width": 40, + "height": 16, + "text": "Role", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role-chevron", + "type": "line", + "x": 668, + "y": 176, + "width": 8, + "height": 5, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [4, 5], [8, 0]] + }, + { + "id": "table-header-row", + "type": "rectangle", + "x": 304, + "y": 208, + "width": 1112, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-member-col", + "type": "text", + "x": 320, + "y": 224, + "width": 60, + "height": 14, + "text": "Member", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-role-col", + "type": "text", + "x": 600, + "y": 224, + "width": 40, + "height": 14, + "text": "Role", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-joined-col", + "type": "text", + "x": 880, + "y": 224, + "width": 50, + "height": 14, + "text": "Joined", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-actions-col", + "type": "text", + "x": 1320, + "y": 224, + "width": 55, + "height": 14, + "text": "Actions", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-1", + "type": "rectangle", + "x": 304, + "y": 256, + "width": 1112, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-avatar", + "type": "ellipse", + "x": 320, + "y": 272, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-avatar-initials", + "type": "text", + "x": 328, + "y": 280, + "width": 16, + "height": 16, + "text": "JD", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-name", + "type": "text", + "x": 364, + "y": 270, + "width": 80, + "height": 14, + "text": "John Doe", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-email", + "type": "text", + "x": 364, + "y": 288, + "width": 130, + "height": 12, + "text": "john@example.com", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-you-badge", + "type": "rectangle", + "x": 500, + "y": 276, + "width": 36, + "height": 18, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 9 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-you-text", + "type": "text", + "x": 510, + "y": 278, + "width": 18, + "height": 14, + "text": "you", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-dropdown", + "type": "rectangle", + "x": 600, + "y": 276, + "width": 100, + "height": 26, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-badge", + "type": "rectangle", + "x": 608, + "y": 280, + "width": 55, + "height": 18, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 25, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 9 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-text", + "type": "text", + "x": 616, + "y": 282, + "width": 40, + "height": 14, + "text": "Owner", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-chevron", + "type": "line", + "x": 680, + "y": 286, + "width": 8, + "height": 5, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [4, 5], [8, 0]] + }, + { + "id": "row-1-joined", + "type": "text", + "x": 880, + "y": 281, + "width": 90, + "height": 14, + "text": "Jan 1, 2024", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-more", + "type": "rectangle", + "x": 1356, + "y": 278, + "width": 28, + "height": 28, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-more-dot1", + "type": "ellipse", + "x": 1364, + "y": 290, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-more-dot2", + "type": "ellipse", + "x": 1370, + "y": 290, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-more-dot3", + "type": "ellipse", + "x": 1376, + "y": 290, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-2-hover", + "type": "rectangle", + "x": 304, + "y": 320, + "width": 1112, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 40, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-avatar", + "type": "ellipse", + "x": 320, + "y": 336, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-avatar-initials", + "type": "text", + "x": 329, + "y": 344, + "width": 14, + "height": 16, + "text": "JS", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-name", + "type": "text", + "x": 364, + "y": 334, + "width": 90, + "height": 14, + "text": "Jane Smith", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-email", + "type": "text", + "x": 364, + "y": 352, + "width": 130, + "height": 12, + "text": "jane@example.com", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-dropdown", + "type": "rectangle", + "x": 600, + "y": 340, + "width": 100, + "height": 26, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-badge", + "type": "rectangle", + "x": 608, + "y": 344, + "width": 55, + "height": 18, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 25, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 9 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-text", + "type": "text", + "x": 618, + "y": 346, + "width": 35, + "height": 14, + "text": "Admin", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-chevron", + "type": "line", + "x": 680, + "y": 350, + "width": 8, + "height": 5, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [4, 5], [8, 0]] + }, + { + "id": "row-2-joined", + "type": "text", + "x": 880, + "y": 345, + "width": 90, + "height": 14, + "text": "Jan 5, 2024", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-remove", + "type": "rectangle", + "x": 1320, + "y": 342, + "width": 28, + "height": 28, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-remove-x1", + "type": "line", + "x": 1328, + "y": 350, + "width": 12, + "height": 12, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [12, 12]] + }, + { + "id": "row-2-action-remove-x2", + "type": "line", + "x": 1328, + "y": 362, + "width": 12, + "height": 12, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [12, -12]] + }, + { + "id": "row-2-action-more", + "type": "rectangle", + "x": 1356, + "y": 342, + "width": 28, + "height": 28, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-more-dot1", + "type": "ellipse", + "x": 1364, + "y": 354, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-more-dot2", + "type": "ellipse", + "x": 1370, + "y": 354, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-more-dot3", + "type": "ellipse", + "x": 1376, + "y": 354, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-3", + "type": "rectangle", + "x": 304, + "y": 384, + "width": 1112, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-avatar", + "type": "ellipse", + "x": 320, + "y": 400, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-avatar-initials", + "type": "text", + "x": 327, + "y": 408, + "width": 18, + "height": 16, + "text": "BW", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-name", + "type": "text", + "x": 364, + "y": 398, + "width": 90, + "height": 14, + "text": "Bob Wilson", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-email", + "type": "text", + "x": 364, + "y": 416, + "width": 130, + "height": 12, + "text": "bob@example.com", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-dropdown", + "type": "rectangle", + "x": 600, + "y": 404, + "width": 100, + "height": 26, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-badge", + "type": "rectangle", + "x": 608, + "y": 408, + "width": 55, + "height": 18, + "strokeColor": "transparent", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 9 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-text", + "type": "text", + "x": 613, + "y": 410, + "width": 45, + "height": 14, + "text": "Member", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-chevron", + "type": "line", + "x": 680, + "y": 414, + "width": 8, + "height": 5, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [4, 5], [8, 0]] + }, + { + "id": "row-3-joined", + "type": "text", + "x": 880, + "y": 409, + "width": 95, + "height": 14, + "text": "Jan 10, 2024", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-remove", + "type": "rectangle", + "x": 1320, + "y": 406, + "width": 28, + "height": 28, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-remove-x1", + "type": "line", + "x": 1328, + "y": 414, + "width": 12, + "height": 12, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [12, 12]] + }, + { + "id": "row-3-action-remove-x2", + "type": "line", + "x": 1328, + "y": 426, + "width": 12, + "height": 12, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [12, -12]] + }, + { + "id": "row-3-action-more", + "type": "rectangle", + "x": 1356, + "y": 406, + "width": 28, + "height": 28, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-more-dot1", + "type": "ellipse", + "x": 1364, + "y": 418, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-more-dot2", + "type": "ellipse", + "x": 1370, + "y": 418, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-more-dot3", + "type": "ellipse", + "x": 1376, + "y": 418, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "empty-rows-area", + "type": "rectangle", + "x": 304, + "y": 448, + "width": 1112, + "height": 372, + "strokeColor": "transparent", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-bar", + "type": "rectangle", + "x": 304, + "y": 820, + "width": 1112, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-info", + "type": "text", + "x": 328, + "y": 836, + "width": 130, + "height": 16, + "text": "Showing 1-10 of 24", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-prev", + "type": "rectangle", + "x": 1244, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-prev-icon", + "type": "line", + "x": 1257, + "y": 838, + "width": 6, + "height": 10, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[6, 0], [0, 5], [6, 10]] + }, + { + "id": "page-1", + "type": "rectangle", + "x": 1284, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-1-text", + "type": "text", + "x": 1296, + "y": 838, + "width": 10, + "height": 12, + "text": "1", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-2", + "type": "rectangle", + "x": 1324, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-2-text", + "type": "text", + "x": 1336, + "y": 838, + "width": 10, + "height": 12, + "text": "2", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-3", + "type": "rectangle", + "x": 1364, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-3-text", + "type": "text", + "x": 1376, + "y": 838, + "width": 10, + "height": 12, + "text": "3", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-next", + "type": "rectangle", + "x": 1404, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-next-icon", + "type": "line", + "x": 1414, + "y": 838, + "width": 6, + "height": 10, + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [6, 5], [0, 10]] + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/high-fidelity/data-table-users.excalidraw b/.context/turbostarter-framework-context/wireframes/high-fidelity/data-table-users.excalidraw new file mode 100644 index 0000000..87eb67c --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/high-fidelity/data-table-users.excalidraw @@ -0,0 +1,2940 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-section-label", + "type": "text", + "x": 20, + "y": 80, + "width": 80, + "height": 14, + "text": "ADMIN", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-icon", + "type": "rectangle", + "x": 32, + "y": 110, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-label", + "type": "text", + "x": 60, + "y": 112, + "width": 80, + "height": 16, + "text": "Dashboard", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-active", + "type": "rectangle", + "x": 20, + "y": 148, + "width": 240, + "height": 40, + "strokeColor": "$primary", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-icon", + "type": "ellipse", + "x": 32, + "y": 158, + "width": 20, + "height": 20, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-label", + "type": "text", + "x": 60, + "y": 160, + "width": 50, + "height": 16, + "text": "Users", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings", + "type": "rectangle", + "x": 20, + "y": 196, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-icon", + "type": "rectangle", + "x": 32, + "y": 206, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-label", + "type": "text", + "x": 60, + "y": 208, + "width": 60, + "height": 16, + "text": "Settings", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-analytics", + "type": "rectangle", + "x": 20, + "y": 244, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-analytics-icon", + "type": "rectangle", + "x": 32, + "y": 254, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-analytics-label", + "type": "text", + "x": 60, + "y": 256, + "width": 70, + "height": 16, + "text": "Analytics", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-avatar", + "type": "ellipse", + "x": 28, + "y": 856, + "width": 24, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-name", + "type": "text", + "x": 60, + "y": 860, + "width": 80, + "height": 16, + "text": "Admin User", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "text", + "x": 304, + "y": 20, + "width": 60, + "height": 24, + "text": "Users", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-count-badge", + "type": "rectangle", + "x": 372, + "y": 20, + "width": 80, + "height": 24, + "strokeColor": "transparent", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-count-text", + "type": "text", + "x": 382, + "y": 24, + "width": 60, + "height": 16, + "text": "1,234 users", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-container", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 1112, + "height": 780, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "toolbar", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 1112, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-input", + "type": "rectangle", + "x": 316, + "y": 98, + "width": 240, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-icon", + "type": "ellipse", + "x": 326, + "y": 106, + "width": 16, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-icon-handle", + "type": "line", + "x": 339, + "y": 119, + "width": 6, + "height": 6, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [6, 6]] + }, + { + "id": "search-placeholder", + "type": "text", + "x": 350, + "y": 108, + "width": 120, + "height": 16, + "text": "Search users...", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role", + "type": "rectangle", + "x": 568, + "y": 98, + "width": 100, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role-text", + "type": "text", + "x": 580, + "y": 108, + "width": 40, + "height": 16, + "text": "Role", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role-chevron", + "type": "line", + "x": 648, + "y": 112, + "width": 8, + "height": 5, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [4, 5], [8, 0]] + }, + { + "id": "filter-status", + "type": "rectangle", + "x": 680, + "y": 98, + "width": 100, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-status-text", + "type": "text", + "x": 692, + "y": 108, + "width": 50, + "height": 16, + "text": "Status", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-status-chevron", + "type": "line", + "x": 760, + "y": 112, + "width": 8, + "height": 5, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [4, 5], [8, 0]] + }, + { + "id": "filter-2fa", + "type": "rectangle", + "x": 792, + "y": 98, + "width": 80, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-2fa-text", + "type": "text", + "x": 804, + "y": 108, + "width": 30, + "height": 16, + "text": "2FA", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-2fa-chevron", + "type": "line", + "x": 852, + "y": 112, + "width": 8, + "height": 5, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [4, 5], [8, 0]] + }, + { + "id": "view-options", + "type": "rectangle", + "x": 1304, + "y": 98, + "width": 100, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "view-options-icon", + "type": "rectangle", + "x": 1316, + "y": 108, + "width": 14, + "height": 14, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "view-options-text", + "type": "text", + "x": 1336, + "y": 108, + "width": 60, + "height": 16, + "text": "Columns", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-header-row", + "type": "rectangle", + "x": 304, + "y": 144, + "width": 1112, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-checkbox", + "type": "rectangle", + "x": 320, + "y": 158, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "header-row-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-user-col", + "type": "text", + "x": 360, + "y": 160, + "width": 40, + "height": 14, + "text": "User", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-role-col", + "type": "text", + "x": 580, + "y": 160, + "width": 40, + "height": 14, + "text": "Role", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-2fa-col", + "type": "text", + "x": 720, + "y": 160, + "width": 30, + "height": 14, + "text": "2FA", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-status-col", + "type": "text", + "x": 840, + "y": 160, + "width": 50, + "height": 14, + "text": "Status", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-created-col", + "type": "text", + "x": 980, + "y": 160, + "width": 60, + "height": 14, + "text": "Created", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-actions-col", + "type": "text", + "x": 1320, + "y": 160, + "width": 55, + "height": 14, + "text": "Actions", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-1", + "type": "rectangle", + "x": 304, + "y": 192, + "width": 1112, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-checkbox", + "type": "rectangle", + "x": 320, + "y": 214, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-avatar", + "type": "ellipse", + "x": 360, + "y": 208, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-avatar-initials", + "type": "text", + "x": 368, + "y": 216, + "width": 16, + "height": 16, + "text": "JD", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-name", + "type": "text", + "x": 404, + "y": 206, + "width": 80, + "height": 14, + "text": "John Doe", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-email", + "type": "text", + "x": 404, + "y": 224, + "width": 130, + "height": 12, + "text": "john@example.com", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-badge", + "type": "rectangle", + "x": 580, + "y": 214, + "width": 60, + "height": 22, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 25, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 11 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-text", + "type": "text", + "x": 594, + "y": 218, + "width": 35, + "height": 14, + "text": "Admin", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-2fa-icon", + "type": "ellipse", + "x": 720, + "y": 216, + "width": 16, + "height": 16, + "strokeColor": "$success", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-2fa-check", + "type": "line", + "x": 724, + "y": 224, + "width": 8, + "height": 6, + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [3, 4], [8, -2]] + }, + { + "id": "row-1-status-badge", + "type": "rectangle", + "x": 840, + "y": 214, + "width": 60, + "height": 22, + "strokeColor": "transparent", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 25, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 11 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status-dot", + "type": "ellipse", + "x": 850, + "y": 221, + "width": 8, + "height": 8, + "strokeColor": "transparent", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status-text", + "type": "text", + "x": 862, + "y": 218, + "width": 35, + "height": 14, + "text": "Active", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$success", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-created", + "type": "text", + "x": 980, + "y": 217, + "width": 90, + "height": 14, + "text": "Jan 15, 2024", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-edit", + "type": "rectangle", + "x": 1320, + "y": 212, + "width": 28, + "height": 28, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-edit-icon", + "type": "rectangle", + "x": 1328, + "y": 220, + "width": 12, + "height": 12, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-more", + "type": "rectangle", + "x": 1356, + "y": 212, + "width": 28, + "height": 28, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-more-dot1", + "type": "ellipse", + "x": 1364, + "y": 224, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-more-dot2", + "type": "ellipse", + "x": 1370, + "y": 224, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-more-dot3", + "type": "ellipse", + "x": 1376, + "y": 224, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-2-hover", + "type": "rectangle", + "x": 304, + "y": 256, + "width": 1112, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 40, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-checkbox", + "type": "rectangle", + "x": 320, + "y": 278, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-avatar", + "type": "ellipse", + "x": 360, + "y": 272, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-avatar-initials", + "type": "text", + "x": 369, + "y": 280, + "width": 14, + "height": 16, + "text": "JS", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-name", + "type": "text", + "x": 404, + "y": 270, + "width": 90, + "height": 14, + "text": "Jane Smith", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-email", + "type": "text", + "x": 404, + "y": 288, + "width": 130, + "height": 12, + "text": "jane@example.com", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-badge", + "type": "rectangle", + "x": 580, + "y": 278, + "width": 50, + "height": 22, + "strokeColor": "transparent", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 11 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-text", + "type": "text", + "x": 592, + "y": 282, + "width": 30, + "height": 14, + "text": "User", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-2fa-icon", + "type": "ellipse", + "x": 720, + "y": 280, + "width": 16, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 50, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-2fa-x1", + "type": "line", + "x": 724, + "y": 284, + "width": 8, + "height": 8, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 50, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [8, 8]] + }, + { + "id": "row-2-2fa-x2", + "type": "line", + "x": 724, + "y": 292, + "width": 8, + "height": 8, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 50, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [8, -8]] + }, + { + "id": "row-2-status-badge", + "type": "rectangle", + "x": 840, + "y": 278, + "width": 60, + "height": 22, + "strokeColor": "transparent", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 25, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 11 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status-dot", + "type": "ellipse", + "x": 850, + "y": 285, + "width": 8, + "height": 8, + "strokeColor": "transparent", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status-text", + "type": "text", + "x": 862, + "y": 282, + "width": 35, + "height": 14, + "text": "Active", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$success", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-created", + "type": "text", + "x": 980, + "y": 281, + "width": 90, + "height": 14, + "text": "Jan 12, 2024", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-edit", + "type": "rectangle", + "x": 1320, + "y": 276, + "width": 28, + "height": 28, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-edit-icon", + "type": "rectangle", + "x": 1328, + "y": 284, + "width": 12, + "height": 12, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-more", + "type": "rectangle", + "x": 1356, + "y": 276, + "width": 28, + "height": 28, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-more-dot1", + "type": "ellipse", + "x": 1364, + "y": 288, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-more-dot2", + "type": "ellipse", + "x": 1370, + "y": 288, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-more-dot3", + "type": "ellipse", + "x": 1376, + "y": 288, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-3", + "type": "rectangle", + "x": 304, + "y": 320, + "width": 1112, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-checkbox", + "type": "rectangle", + "x": 320, + "y": 342, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-avatar", + "type": "ellipse", + "x": 360, + "y": 336, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-avatar-initials", + "type": "text", + "x": 367, + "y": 344, + "width": 18, + "height": 16, + "text": "BW", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-name", + "type": "text", + "x": 404, + "y": 334, + "width": 90, + "height": 14, + "text": "Bob Wilson", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-email", + "type": "text", + "x": 404, + "y": 352, + "width": 130, + "height": 12, + "text": "bob@example.com", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-badge", + "type": "rectangle", + "x": 580, + "y": 342, + "width": 50, + "height": 22, + "strokeColor": "transparent", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 11 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-text", + "type": "text", + "x": 592, + "y": 346, + "width": 30, + "height": 14, + "text": "User", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-2fa-icon", + "type": "ellipse", + "x": 720, + "y": 344, + "width": 16, + "height": 16, + "strokeColor": "$success", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-2fa-check", + "type": "line", + "x": 724, + "y": 352, + "width": 8, + "height": 6, + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [3, 4], [8, -2]] + }, + { + "id": "row-3-status-badge", + "type": "rectangle", + "x": 840, + "y": 342, + "width": 62, + "height": 22, + "strokeColor": "transparent", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 25, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 11 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-status-dot", + "type": "ellipse", + "x": 850, + "y": 349, + "width": 8, + "height": 8, + "strokeColor": "transparent", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-status-text", + "type": "text", + "x": 862, + "y": 346, + "width": 40, + "height": 14, + "text": "Banned", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-created", + "type": "text", + "x": 980, + "y": 345, + "width": 90, + "height": 14, + "text": "Jan 10, 2024", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-edit", + "type": "rectangle", + "x": 1320, + "y": 340, + "width": 28, + "height": 28, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-edit-icon", + "type": "rectangle", + "x": 1328, + "y": 348, + "width": 12, + "height": 12, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-more", + "type": "rectangle", + "x": 1356, + "y": 340, + "width": 28, + "height": 28, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-more-dot1", + "type": "ellipse", + "x": 1364, + "y": 352, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-more-dot2", + "type": "ellipse", + "x": 1370, + "y": 352, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-more-dot3", + "type": "ellipse", + "x": 1376, + "y": 352, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "empty-rows-area", + "type": "rectangle", + "x": 304, + "y": 384, + "width": 1112, + "height": 384, + "strokeColor": "transparent", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-bar", + "type": "rectangle", + "x": 304, + "y": 820, + "width": 1112, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-info", + "type": "text", + "x": 328, + "y": 836, + "width": 150, + "height": 16, + "text": "Showing 1-10 of 1,234", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-first", + "type": "rectangle", + "x": 1084, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-first-icon1", + "type": "line", + "x": 1094, + "y": 838, + "width": 6, + "height": 10, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[6, 0], [0, 5], [6, 10]] + }, + { + "id": "page-first-icon2", + "type": "line", + "x": 1100, + "y": 838, + "width": 6, + "height": 10, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[6, 0], [0, 5], [6, 10]] + }, + { + "id": "page-prev", + "type": "rectangle", + "x": 1124, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-prev-icon", + "type": "line", + "x": 1137, + "y": 838, + "width": 6, + "height": 10, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[6, 0], [0, 5], [6, 10]] + }, + { + "id": "page-1", + "type": "rectangle", + "x": 1164, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-1-text", + "type": "text", + "x": 1176, + "y": 838, + "width": 10, + "height": 12, + "text": "1", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-2", + "type": "rectangle", + "x": 1204, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-2-text", + "type": "text", + "x": 1216, + "y": 838, + "width": 10, + "height": 12, + "text": "2", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-3", + "type": "rectangle", + "x": 1244, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-3-text", + "type": "text", + "x": 1256, + "y": 838, + "width": 10, + "height": 12, + "text": "3", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-ellipsis", + "type": "text", + "x": 1284, + "y": 838, + "width": 20, + "height": 12, + "text": "...", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-last-num", + "type": "rectangle", + "x": 1316, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-last-num-text", + "type": "text", + "x": 1322, + "y": 838, + "width": 20, + "height": 12, + "text": "124", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-next", + "type": "rectangle", + "x": 1356, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-next-icon", + "type": "line", + "x": 1366, + "y": 838, + "width": 6, + "height": 10, + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [6, 5], [0, 10]] + }, + { + "id": "page-last", + "type": "rectangle", + "x": 1396, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-last-icon1", + "type": "line", + "x": 1404, + "y": 838, + "width": 6, + "height": 10, + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [6, 5], [0, 10]] + }, + { + "id": "page-last-icon2", + "type": "line", + "x": 1410, + "y": 838, + "width": 6, + "height": 10, + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [6, 5], [0, 10]] + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/high-fidelity/settings-billing.excalidraw b/.context/turbostarter-framework-context/wireframes/high-fidelity/settings-billing.excalidraw new file mode 100644 index 0000000..d50b5ea --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/high-fidelity/settings-billing.excalidraw @@ -0,0 +1,2451 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-text", + "type": "text", + "x": 40, + "y": 24, + "width": 80, + "height": 16, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-icon-1", + "type": "rectangle", + "x": 32, + "y": 108, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-1", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-1-text", + "type": "text", + "x": 60, + "y": 112, + "width": 80, + "height": 16, + "text": "Dashboard", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-icon-2", + "type": "rectangle", + "x": 32, + "y": 158, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-2", + "type": "rectangle", + "x": 20, + "y": 150, + "width": 240, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-2-text", + "type": "text", + "x": 60, + "y": 162, + "width": 80, + "height": 16, + "text": "Browse MCPs", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-icon-3", + "type": "rectangle", + "x": 32, + "y": 208, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-3", + "type": "rectangle", + "x": 20, + "y": 200, + "width": 240, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-3-text", + "type": "text", + "x": 60, + "y": 212, + "width": 80, + "height": 16, + "text": "Bundles", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-icon-settings", + "type": "rectangle", + "x": 32, + "y": 268, + "width": 20, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-active", + "type": "rectangle", + "x": 20, + "y": 260, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-text", + "type": "text", + "x": 60, + "y": 272, + "width": 80, + "height": 16, + "text": "Settings", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-avatar", + "type": "ellipse", + "x": 28, + "y": 856, + "width": 28, + "height": 28, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 848, + "width": 240, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user-text", + "type": "text", + "x": 64, + "y": 856, + "width": 100, + "height": 14, + "text": "John Doe", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-email", + "type": "text", + "x": 64, + "y": 872, + "width": 140, + "height": 12, + "text": "john@example.com", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title-text", + "type": "text", + "x": 304, + "y": 22, + "width": 100, + "height": 20, + "text": "Settings", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-breadcrumb", + "type": "text", + "x": 304, + "y": 44, + "width": 200, + "height": 12, + "text": "Dashboard / Settings / Billing", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tabs-container", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 300, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-general", + "type": "rectangle", + "x": 308, + "y": 92, + "width": 92, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-general-text", + "type": "text", + "x": 330, + "y": 100, + "width": 48, + "height": 16, + "text": "General", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["tabs-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-security", + "type": "rectangle", + "x": 404, + "y": 92, + "width": 92, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-security-text", + "type": "text", + "x": 424, + "y": 100, + "width": 56, + "height": 16, + "text": "Security", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["tabs-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-billing-active", + "type": "rectangle", + "x": 500, + "y": 92, + "width": 92, + "height": 32, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-billing-text", + "type": "text", + "x": 528, + "y": 100, + "width": 40, + "height": 16, + "text": "Billing", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-card", + "type": "rectangle", + "x": 304, + "y": 152, + "width": 560, + "height": 240, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["plan-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-card-shadow", + "type": "rectangle", + "x": 306, + "y": 154, + "width": 560, + "height": 240, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": ["plan-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-icon-bg", + "type": "rectangle", + "x": 328, + "y": 176, + "width": 48, + "height": 48, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 15, + "groupIds": ["plan-section"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-icon", + "type": "rectangle", + "x": 340, + "y": 188, + "width": 24, + "height": 24, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["plan-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-title-text", + "type": "text", + "x": 392, + "y": 176, + "width": 120, + "height": 20, + "text": "Current Plan", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["plan-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-name-badge", + "type": "rectangle", + "x": 392, + "y": 204, + "width": 52, + "height": 24, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["plan-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-name-badge-text", + "type": "text", + "x": 408, + "y": 208, + "width": 20, + "height": 16, + "text": "Pro", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["plan-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "most-popular-badge", + "type": "rectangle", + "x": 452, + "y": 204, + "width": 100, + "height": 24, + "strokeColor": "$success", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 15, + "groupIds": ["plan-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "most-popular-badge-text", + "type": "text", + "x": 462, + "y": 208, + "width": 80, + "height": 16, + "text": "Most Popular", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$success", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["plan-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-price-large", + "type": "text", + "x": 328, + "y": 244, + "width": 60, + "height": 36, + "text": "$29", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["plan-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-price-period", + "type": "text", + "x": 392, + "y": 258, + "width": 50, + "height": 16, + "text": "/month", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["plan-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-feature-1-check", + "type": "ellipse", + "x": 328, + "y": 296, + "width": 16, + "height": 16, + "strokeColor": "$success", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["plan-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-feature-1-text", + "type": "text", + "x": 352, + "y": 296, + "width": 200, + "height": 16, + "text": "Unlimited MCP installations", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["plan-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-feature-2-check", + "type": "ellipse", + "x": 328, + "y": 320, + "width": 16, + "height": 16, + "strokeColor": "$success", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["plan-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-feature-2-text", + "type": "text", + "x": 352, + "y": 320, + "width": 180, + "height": 16, + "text": "Priority support", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["plan-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-feature-3-check", + "type": "ellipse", + "x": 328, + "y": 344, + "width": 16, + "height": 16, + "strokeColor": "$success", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["plan-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-feature-3-text", + "type": "text", + "x": 352, + "y": 344, + "width": 160, + "height": 16, + "text": "API access included", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["plan-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "manage-subscription-button", + "type": "rectangle", + "x": 700, + "y": 176, + "width": 140, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["plan-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "manage-subscription-button-text", + "type": "text", + "x": 710, + "y": 190, + "width": 120, + "height": 16, + "text": "Change plan", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["plan-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-card", + "type": "rectangle", + "x": 880, + "y": 152, + "width": 280, + "height": 240, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["credits-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-card-shadow", + "type": "rectangle", + "x": 882, + "y": 154, + "width": 280, + "height": 240, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": ["credits-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-title-text", + "type": "text", + "x": 904, + "y": 176, + "width": 80, + "height": 20, + "text": "Credits", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["credits-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-balance-number", + "type": "text", + "x": 904, + "y": 212, + "width": 100, + "height": 36, + "text": "2,450", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["credits-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-balance-label", + "type": "text", + "x": 904, + "y": 252, + "width": 120, + "height": 14, + "text": "credits remaining", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["credits-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-usage-bar-bg", + "type": "rectangle", + "x": 904, + "y": 284, + "width": 232, + "height": 12, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 40, + "groupIds": ["credits-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-usage-bar-fill", + "type": "rectangle", + "x": 904, + "y": 284, + "width": 140, + "height": 12, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["credits-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-usage-text", + "type": "text", + "x": 904, + "y": 304, + "width": 200, + "height": 12, + "text": "2,450 of 5,000 credits used (49%)", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["credits-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "buy-credits-button", + "type": "rectangle", + "x": 904, + "y": 336, + "width": 140, + "height": 44, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["credits-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "buy-credits-button-text", + "type": "text", + "x": 940, + "y": 350, + "width": 68, + "height": 16, + "text": "Buy credits", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["credits-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credit-history-link", + "type": "text", + "x": 1052, + "y": 350, + "width": 100, + "height": 16, + "text": "View history", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["credits-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "payment-method-card", + "type": "rectangle", + "x": 304, + "y": 416, + "width": 560, + "height": 120, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["payment-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "payment-card-shadow", + "type": "rectangle", + "x": 306, + "y": 418, + "width": 560, + "height": 120, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": ["payment-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "payment-title-text", + "type": "text", + "x": 328, + "y": 440, + "width": 140, + "height": 20, + "text": "Payment Method", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["payment-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-brand-icon", + "type": "rectangle", + "x": 328, + "y": 480, + "width": 48, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["payment-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-brand-text", + "type": "text", + "x": 338, + "y": 488, + "width": 28, + "height": 16, + "text": "VISA", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["payment-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-masked-number", + "type": "text", + "x": 392, + "y": 480, + "width": 180, + "height": 16, + "text": "**** **** **** 4242", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["payment-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-expiry", + "type": "text", + "x": 392, + "y": 500, + "width": 100, + "height": 12, + "text": "Expires 12/25", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["payment-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "update-payment-button", + "type": "rectangle", + "x": 760, + "y": 476, + "width": 80, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["payment-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "update-payment-button-text", + "type": "text", + "x": 778, + "y": 488, + "width": 44, + "height": 16, + "text": "Update", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["payment-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "billing-history-card", + "type": "rectangle", + "x": 304, + "y": 560, + "width": 856, + "height": 300, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "billing-history-shadow", + "type": "rectangle", + "x": 306, + "y": 562, + "width": 856, + "height": 300, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "history-title-text", + "type": "text", + "x": 328, + "y": 584, + "width": 140, + "height": 20, + "text": "Billing History", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "view-all-invoices", + "type": "text", + "x": 1060, + "y": 586, + "width": 80, + "height": 16, + "text": "View all", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-header", + "type": "rectangle", + "x": 328, + "y": 620, + "width": 808, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 30, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-date-text", + "type": "text", + "x": 352, + "y": 632, + "width": 60, + "height": 16, + "text": "Date", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-description-text", + "type": "text", + "x": 520, + "y": 632, + "width": 80, + "height": 16, + "text": "Description", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-amount-text", + "type": "text", + "x": 780, + "y": 632, + "width": 60, + "height": 16, + "text": "Amount", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-status-text", + "type": "text", + "x": 900, + "y": 632, + "width": 50, + "height": 16, + "text": "Status", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invoice-text", + "type": "text", + "x": 1020, + "y": 632, + "width": 60, + "height": 16, + "text": "Invoice", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1", + "type": "rectangle", + "x": 328, + "y": 660, + "width": 808, + "height": 52, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-date-text", + "type": "text", + "x": 352, + "y": 678, + "width": 100, + "height": 16, + "text": "Jan 1, 2026", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-desc-text", + "type": "text", + "x": 520, + "y": 678, + "width": 180, + "height": 16, + "text": "Pro Plan - Monthly", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-amount-text", + "type": "text", + "x": 780, + "y": 678, + "width": 60, + "height": 16, + "text": "$29.00", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status", + "type": "rectangle", + "x": 900, + "y": 672, + "width": 52, + "height": 24, + "strokeColor": "$success", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 15, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status-text", + "type": "text", + "x": 912, + "y": 676, + "width": 28, + "height": 16, + "text": "Paid", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$success", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-pdf-icon", + "type": "rectangle", + "x": 1020, + "y": 672, + "width": 24, + "height": 24, + "strokeColor": "$destructive", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 20, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-pdf-text", + "type": "text", + "x": 1024, + "y": 676, + "width": 16, + "height": 16, + "text": "PDF", + "fontSize": 8, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-download-text", + "type": "text", + "x": 1052, + "y": 678, + "width": 60, + "height": 16, + "text": "Download", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2", + "type": "rectangle", + "x": 328, + "y": 712, + "width": 808, + "height": 52, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-date-text", + "type": "text", + "x": 352, + "y": 730, + "width": 100, + "height": 16, + "text": "Dec 1, 2025", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-desc-text", + "type": "text", + "x": 520, + "y": 730, + "width": 180, + "height": 16, + "text": "Pro Plan - Monthly", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-amount-text", + "type": "text", + "x": 780, + "y": 730, + "width": 60, + "height": 16, + "text": "$29.00", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status", + "type": "rectangle", + "x": 900, + "y": 724, + "width": 52, + "height": 24, + "strokeColor": "$success", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 15, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status-text", + "type": "text", + "x": 912, + "y": 728, + "width": 28, + "height": 16, + "text": "Paid", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$success", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-pdf-icon", + "type": "rectangle", + "x": 1020, + "y": 724, + "width": 24, + "height": 24, + "strokeColor": "$destructive", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 20, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-pdf-text", + "type": "text", + "x": 1024, + "y": 728, + "width": 16, + "height": 16, + "text": "PDF", + "fontSize": 8, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-download-text", + "type": "text", + "x": 1052, + "y": 730, + "width": 60, + "height": 16, + "text": "Download", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3", + "type": "rectangle", + "x": 328, + "y": 764, + "width": 808, + "height": 52, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-date-text", + "type": "text", + "x": 352, + "y": 782, + "width": 100, + "height": 16, + "text": "Nov 1, 2025", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-desc-text", + "type": "text", + "x": 520, + "y": 782, + "width": 180, + "height": 16, + "text": "Credit Purchase", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-amount-text", + "type": "text", + "x": 780, + "y": 782, + "width": 60, + "height": 16, + "text": "$49.00", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-status", + "type": "rectangle", + "x": 896, + "y": 776, + "width": 64, + "height": 24, + "strokeColor": "$warning", + "backgroundColor": "$warning", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 15, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-status-text", + "type": "text", + "x": 904, + "y": 780, + "width": 48, + "height": 16, + "text": "Pending", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$warning", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-pdf-icon", + "type": "rectangle", + "x": 1020, + "y": 776, + "width": 24, + "height": 24, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 40, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-pending-text", + "type": "text", + "x": 1052, + "y": 782, + "width": 60, + "height": 16, + "text": "Pending", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/high-fidelity/settings-general.excalidraw b/.context/turbostarter-framework-context/wireframes/high-fidelity/settings-general.excalidraw new file mode 100644 index 0000000..60bb187 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/high-fidelity/settings-general.excalidraw @@ -0,0 +1,1647 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-text", + "type": "text", + "x": 40, + "y": 24, + "width": 80, + "height": 16, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-icon-1", + "type": "rectangle", + "x": 32, + "y": 108, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-1", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-1-text", + "type": "text", + "x": 60, + "y": 112, + "width": 80, + "height": 16, + "text": "Dashboard", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-icon-2", + "type": "rectangle", + "x": 32, + "y": 158, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-2", + "type": "rectangle", + "x": 20, + "y": 150, + "width": 240, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-2-text", + "type": "text", + "x": 60, + "y": 162, + "width": 80, + "height": 16, + "text": "Browse MCPs", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-icon-3", + "type": "rectangle", + "x": 32, + "y": 208, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-3", + "type": "rectangle", + "x": 20, + "y": 200, + "width": 240, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-3-text", + "type": "text", + "x": 60, + "y": 212, + "width": 80, + "height": 16, + "text": "Bundles", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-icon-settings", + "type": "rectangle", + "x": 32, + "y": 268, + "width": 20, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-active", + "type": "rectangle", + "x": 20, + "y": 260, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-text", + "type": "text", + "x": 60, + "y": 272, + "width": 80, + "height": 16, + "text": "Settings", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-avatar", + "type": "ellipse", + "x": 28, + "y": 856, + "width": 28, + "height": 28, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 848, + "width": 240, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user-text", + "type": "text", + "x": 64, + "y": 856, + "width": 100, + "height": 14, + "text": "John Doe", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-email", + "type": "text", + "x": 64, + "y": 872, + "width": 140, + "height": 12, + "text": "john@example.com", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title-text", + "type": "text", + "x": 304, + "y": 22, + "width": 100, + "height": 20, + "text": "Settings", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-breadcrumb", + "type": "text", + "x": 304, + "y": 44, + "width": 200, + "height": 12, + "text": "Dashboard / Settings / General", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tabs-container", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 300, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-general-active", + "type": "rectangle", + "x": 308, + "y": 92, + "width": 92, + "height": 32, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-general-text", + "type": "text", + "x": 330, + "y": 100, + "width": 48, + "height": 16, + "text": "General", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-security", + "type": "rectangle", + "x": 404, + "y": 92, + "width": 92, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-security-text", + "type": "text", + "x": 424, + "y": 100, + "width": 56, + "height": 16, + "text": "Security", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["tabs-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-billing", + "type": "rectangle", + "x": 500, + "y": 92, + "width": 92, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-billing-text", + "type": "text", + "x": 528, + "y": 100, + "width": 40, + "height": 16, + "text": "Billing", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["tabs-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "profile-card", + "type": "rectangle", + "x": 304, + "y": 152, + "width": 720, + "height": 320, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "profile-card-shadow", + "type": "rectangle", + "x": 306, + "y": 154, + "width": 720, + "height": 320, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": ["profile-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "profile-card-title-text", + "type": "text", + "x": 328, + "y": 176, + "width": 100, + "height": 20, + "text": "Profile", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "profile-card-subtitle", + "type": "text", + "x": 328, + "y": 200, + "width": 300, + "height": 14, + "text": "Manage your personal information and preferences", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["profile-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "avatar-circle", + "type": "ellipse", + "x": 328, + "y": 232, + "width": 80, + "height": 80, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "avatar-initials", + "type": "text", + "x": 348, + "y": 262, + "width": 40, + "height": 20, + "text": "JD", + "fontSize": 22, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["profile-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "avatar-camera-overlay", + "type": "ellipse", + "x": 380, + "y": 284, + "width": 28, + "height": 28, + "strokeColor": "$background", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "avatar-camera-icon", + "type": "rectangle", + "x": 388, + "y": 292, + "width": 12, + "height": 12, + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "avatar-change-button", + "type": "rectangle", + "x": 328, + "y": 328, + "width": 100, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "avatar-change-button-text", + "type": "text", + "x": 338, + "y": 338, + "width": 80, + "height": 16, + "text": "Change photo", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-label-text", + "type": "text", + "x": 440, + "y": 232, + "width": 60, + "height": 14, + "text": "Full name", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-input", + "type": "rectangle", + "x": 440, + "y": 252, + "width": 560, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-input-text", + "type": "text", + "x": 456, + "y": 266, + "width": 80, + "height": 16, + "text": "John Doe", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-label-text", + "type": "text", + "x": 440, + "y": 312, + "width": 60, + "height": 14, + "text": "Email address", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-input", + "type": "rectangle", + "x": 440, + "y": 332, + "width": 460, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-input-text", + "type": "text", + "x": 456, + "y": 346, + "width": 140, + "height": 16, + "text": "john@example.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "verified-badge", + "type": "rectangle", + "x": 912, + "y": 340, + "width": 88, + "height": 28, + "strokeColor": "$success", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 15, + "groupIds": ["profile-section"], + "roundness": { "type": 3, "value": 14 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "verified-badge-border", + "type": "rectangle", + "x": 912, + "y": 340, + "width": 88, + "height": 28, + "strokeColor": "$success", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": { "type": 3, "value": 14 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "verified-checkmark", + "type": "ellipse", + "x": 920, + "y": 346, + "width": 16, + "height": 16, + "strokeColor": "$success", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "verified-badge-text", + "type": "text", + "x": 940, + "y": 346, + "width": 52, + "height": 16, + "text": "Verified", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$success", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "save-profile-button", + "type": "rectangle", + "x": 328, + "y": 400, + "width": 672, + "height": 44, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "save-profile-button-text", + "type": "text", + "x": 624, + "y": 414, + "width": 80, + "height": 16, + "text": "Save changes", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-card", + "type": "rectangle", + "x": 304, + "y": 496, + "width": 720, + "height": 160, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["language-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-card-shadow", + "type": "rectangle", + "x": 306, + "y": 498, + "width": 720, + "height": 160, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": ["language-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-card-title-text", + "type": "text", + "x": 328, + "y": 520, + "width": 120, + "height": 20, + "text": "Language & Region", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["language-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-card-subtitle", + "type": "text", + "x": 328, + "y": 544, + "width": 280, + "height": 14, + "text": "Set your preferred display language and region", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["language-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-label-text", + "type": "text", + "x": 328, + "y": 576, + "width": 120, + "height": 14, + "text": "Display language", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["language-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-dropdown", + "type": "rectangle", + "x": 328, + "y": 596, + "width": 320, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["language-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-flag-icon", + "type": "rectangle", + "x": 344, + "y": 608, + "width": 24, + "height": 18, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["language-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-dropdown-text", + "type": "text", + "x": 380, + "y": 610, + "width": 100, + "height": 16, + "text": "English (US)", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["language-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "dropdown-chevron", + "type": "rectangle", + "x": 620, + "y": 612, + "width": 16, + "height": 12, + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 60, + "groupIds": ["language-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "danger-zone-card", + "type": "rectangle", + "x": 304, + "y": 680, + "width": 720, + "height": 180, + "strokeColor": "$destructive", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "groupIds": ["danger-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "danger-zone-header", + "type": "rectangle", + "x": 328, + "y": 704, + "width": 672, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["danger-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "danger-zone-warning-icon", + "type": "rectangle", + "x": 328, + "y": 708, + "width": 24, + "height": 24, + "strokeColor": "$destructive", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 20, + "groupIds": ["danger-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "danger-zone-warning-icon-inner", + "type": "text", + "x": 335, + "y": 710, + "width": 10, + "height": 20, + "text": "!", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["danger-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "danger-zone-title-text", + "type": "text", + "x": 360, + "y": 708, + "width": 120, + "height": 20, + "text": "Danger Zone", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["danger-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "danger-zone-description-text", + "type": "text", + "x": 328, + "y": 752, + "width": 500, + "height": 16, + "text": "Once you delete your account, there is no going back. Please be certain.", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["danger-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "danger-zone-description-text-2", + "type": "text", + "x": 328, + "y": 772, + "width": 500, + "height": 16, + "text": "This action will permanently delete your account and all associated data.", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["danger-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "delete-account-button", + "type": "rectangle", + "x": 328, + "y": 808, + "width": 180, + "height": 44, + "strokeColor": "$destructive", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["danger-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "delete-account-button-icon", + "type": "rectangle", + "x": 352, + "y": 820, + "width": 18, + "height": 18, + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["danger-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "delete-account-button-text", + "type": "text", + "x": 378, + "y": 822, + "width": 108, + "height": 16, + "text": "Delete Account", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["danger-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/high-fidelity/settings-security.excalidraw b/.context/turbostarter-framework-context/wireframes/high-fidelity/settings-security.excalidraw new file mode 100644 index 0000000..8439947 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/high-fidelity/settings-security.excalidraw @@ -0,0 +1,1921 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-text", + "type": "text", + "x": 40, + "y": 24, + "width": 80, + "height": 16, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-icon-1", + "type": "rectangle", + "x": 32, + "y": 108, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-1", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-1-text", + "type": "text", + "x": 60, + "y": 112, + "width": 80, + "height": 16, + "text": "Dashboard", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-icon-2", + "type": "rectangle", + "x": 32, + "y": 158, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-2", + "type": "rectangle", + "x": 20, + "y": 150, + "width": 240, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-2-text", + "type": "text", + "x": 60, + "y": 162, + "width": 80, + "height": 16, + "text": "Browse MCPs", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-icon-3", + "type": "rectangle", + "x": 32, + "y": 208, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-3", + "type": "rectangle", + "x": 20, + "y": 200, + "width": 240, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-3-text", + "type": "text", + "x": 60, + "y": 212, + "width": 80, + "height": 16, + "text": "Bundles", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-icon-settings", + "type": "rectangle", + "x": 32, + "y": 268, + "width": 20, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-active", + "type": "rectangle", + "x": 20, + "y": 260, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-text", + "type": "text", + "x": 60, + "y": 272, + "width": 80, + "height": 16, + "text": "Settings", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-avatar", + "type": "ellipse", + "x": 28, + "y": 856, + "width": 28, + "height": 28, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 848, + "width": 240, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user-text", + "type": "text", + "x": 64, + "y": 856, + "width": 100, + "height": 14, + "text": "John Doe", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-email", + "type": "text", + "x": 64, + "y": 872, + "width": 140, + "height": 12, + "text": "john@example.com", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title-text", + "type": "text", + "x": 304, + "y": 22, + "width": 100, + "height": 20, + "text": "Settings", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-breadcrumb", + "type": "text", + "x": 304, + "y": 44, + "width": 200, + "height": 12, + "text": "Dashboard / Settings / Security", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tabs-container", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 300, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-general", + "type": "rectangle", + "x": 308, + "y": 92, + "width": 92, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-general-text", + "type": "text", + "x": 330, + "y": 100, + "width": 48, + "height": 16, + "text": "General", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["tabs-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-security-active", + "type": "rectangle", + "x": 404, + "y": 92, + "width": 92, + "height": 32, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-security-text", + "type": "text", + "x": 424, + "y": 100, + "width": 56, + "height": 16, + "text": "Security", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-billing", + "type": "rectangle", + "x": 500, + "y": 92, + "width": 92, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-billing-text", + "type": "text", + "x": 528, + "y": 100, + "width": 40, + "height": 16, + "text": "Billing", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["tabs-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-card", + "type": "rectangle", + "x": 304, + "y": 152, + "width": 720, + "height": 160, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["twofa-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-card-shadow", + "type": "rectangle", + "x": 306, + "y": 154, + "width": 720, + "height": 160, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": ["twofa-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-shield-icon-bg", + "type": "rectangle", + "x": 328, + "y": 176, + "width": 40, + "height": 40, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 15, + "groupIds": ["twofa-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-shield-icon", + "type": "rectangle", + "x": 338, + "y": 186, + "width": 20, + "height": 20, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["twofa-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-title-text", + "type": "text", + "x": 384, + "y": 180, + "width": 200, + "height": 20, + "text": "Two-Factor Authentication", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["twofa-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-status-indicator", + "type": "rectangle", + "x": 384, + "y": 208, + "width": 80, + "height": 24, + "strokeColor": "$success", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 15, + "groupIds": ["twofa-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-status-dot", + "type": "ellipse", + "x": 392, + "y": 216, + "width": 8, + "height": 8, + "strokeColor": "$success", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["twofa-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-status-text", + "type": "text", + "x": 406, + "y": 212, + "width": 48, + "height": 16, + "text": "Enabled", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$success", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["twofa-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-description-text", + "type": "text", + "x": 328, + "y": 248, + "width": 500, + "height": 16, + "text": "Add an extra layer of security to your account with two-factor authentication.", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["twofa-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-recovery-hint", + "type": "text", + "x": 328, + "y": 268, + "width": 300, + "height": 14, + "text": "Recovery codes available in your backup settings.", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["twofa-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-toggle-track", + "type": "rectangle", + "x": 952, + "y": 188, + "width": 48, + "height": 28, + "strokeColor": "$success", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["twofa-section"], + "roundness": { "type": 3, "value": 14 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-toggle-thumb", + "type": "ellipse", + "x": 976, + "y": 192, + "width": 20, + "height": 20, + "strokeColor": "$background", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["twofa-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkeys-card", + "type": "rectangle", + "x": 304, + "y": 336, + "width": 720, + "height": 240, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["passkeys-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkeys-card-shadow", + "type": "rectangle", + "x": 306, + "y": 338, + "width": 720, + "height": 240, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": ["passkeys-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkeys-fingerprint-bg", + "type": "rectangle", + "x": 328, + "y": 360, + "width": 40, + "height": 40, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 15, + "groupIds": ["passkeys-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkeys-fingerprint-icon", + "type": "ellipse", + "x": 338, + "y": 370, + "width": 20, + "height": 20, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["passkeys-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkeys-title-text", + "type": "text", + "x": 384, + "y": 364, + "width": 100, + "height": 20, + "text": "Passkeys", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["passkeys-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkeys-subtitle", + "type": "text", + "x": 384, + "y": 388, + "width": 300, + "height": 14, + "text": "Use biometrics or security keys to sign in", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["passkeys-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-item-1", + "type": "rectangle", + "x": 328, + "y": 420, + "width": 672, + "height": 52, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["passkeys-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-1-laptop-icon", + "type": "rectangle", + "x": 344, + "y": 434, + "width": 24, + "height": 18, + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 60, + "groupIds": ["passkeys-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-1-laptop-base", + "type": "rectangle", + "x": 340, + "y": 452, + "width": 32, + "height": 4, + "strokeColor": "$muted-foreground", + "backgroundColor": "$muted-foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["passkeys-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-1-name-text", + "type": "text", + "x": 388, + "y": 434, + "width": 120, + "height": 16, + "text": "MacBook Pro", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["passkeys-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-1-date-text", + "type": "text", + "x": 388, + "y": 452, + "width": 160, + "height": 12, + "text": "Last used: 2 hours ago", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["passkeys-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-1-delete", + "type": "rectangle", + "x": 928, + "y": 432, + "width": 60, + "height": 28, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["passkeys-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-1-delete-text", + "type": "text", + "x": 940, + "y": 438, + "width": 36, + "height": 16, + "text": "Delete", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["passkeys-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-item-2", + "type": "rectangle", + "x": 328, + "y": 480, + "width": 672, + "height": 52, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["passkeys-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-2-phone-icon", + "type": "rectangle", + "x": 348, + "y": 492, + "width": 16, + "height": 24, + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 60, + "groupIds": ["passkeys-section"], + "roundness": { "type": 3, "value": 3 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-2-name-text", + "type": "text", + "x": 388, + "y": 494, + "width": 140, + "height": 16, + "text": "iPhone 15 Pro", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["passkeys-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-2-date-text", + "type": "text", + "x": 388, + "y": 512, + "width": 160, + "height": 12, + "text": "Last used: Yesterday", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["passkeys-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-2-delete", + "type": "rectangle", + "x": 928, + "y": 492, + "width": 60, + "height": 28, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["passkeys-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-2-delete-text", + "type": "text", + "x": 940, + "y": 498, + "width": 36, + "height": 16, + "text": "Delete", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["passkeys-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "add-passkey-button", + "type": "rectangle", + "x": 328, + "y": 544, + "width": 140, + "height": 44, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["passkeys-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "add-passkey-button-text", + "type": "text", + "x": 360, + "y": 558, + "width": 76, + "height": 16, + "text": "Add passkey", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["passkeys-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sessions-card", + "type": "rectangle", + "x": 304, + "y": 600, + "width": 720, + "height": 270, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sessions-card-shadow", + "type": "rectangle", + "x": 306, + "y": 602, + "width": 720, + "height": 270, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sessions-title-text", + "type": "text", + "x": 328, + "y": 624, + "width": 140, + "height": 20, + "text": "Active Sessions", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sessions-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sessions-subtitle", + "type": "text", + "x": 328, + "y": 648, + "width": 400, + "height": 14, + "text": "Manage devices and locations where you're signed in", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["sessions-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-item-1", + "type": "rectangle", + "x": 328, + "y": 680, + "width": 672, + "height": 60, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-device-icon", + "type": "rectangle", + "x": 344, + "y": 696, + "width": 24, + "height": 18, + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 60, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-browser-icon", + "type": "ellipse", + "x": 350, + "y": 700, + "width": 12, + "height": 12, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["sessions-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-name-text", + "type": "text", + "x": 388, + "y": 692, + "width": 160, + "height": 16, + "text": "Chrome on macOS", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sessions-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-location-icon", + "type": "ellipse", + "x": 388, + "y": 714, + "width": 8, + "height": 8, + "strokeColor": "$muted-foreground", + "backgroundColor": "$muted-foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 40, + "groupIds": ["sessions-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-location-text", + "type": "text", + "x": 402, + "y": 712, + "width": 200, + "height": 12, + "text": "San Francisco, CA - 192.168.x.x", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["sessions-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-current-dot", + "type": "ellipse", + "x": 916, + "y": 702, + "width": 10, + "height": 10, + "strokeColor": "$success", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["sessions-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-current-badge", + "type": "rectangle", + "x": 932, + "y": 696, + "width": 60, + "height": 24, + "strokeColor": "$success", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 15, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-current-badge-text", + "type": "text", + "x": 942, + "y": 700, + "width": 44, + "height": 16, + "text": "Current", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$success", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sessions-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-item-2", + "type": "rectangle", + "x": 328, + "y": 752, + "width": 672, + "height": 60, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-phone-icon", + "type": "rectangle", + "x": 348, + "y": 768, + "width": 16, + "height": 24, + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 60, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 3 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-name-text", + "type": "text", + "x": 388, + "y": 764, + "width": 140, + "height": 16, + "text": "Safari on iPhone", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sessions-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-location-icon", + "type": "ellipse", + "x": 388, + "y": 786, + "width": 8, + "height": 8, + "strokeColor": "$muted-foreground", + "backgroundColor": "$muted-foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 40, + "groupIds": ["sessions-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-location-text", + "type": "text", + "x": 402, + "y": 784, + "width": 200, + "height": 12, + "text": "San Francisco, CA - 192.168.x.x", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["sessions-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-revoke", + "type": "rectangle", + "x": 928, + "y": 768, + "width": 60, + "height": 28, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-revoke-text", + "type": "text", + "x": 938, + "y": 774, + "width": 44, + "height": 16, + "text": "Revoke", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sessions-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "revoke-all-link", + "type": "text", + "x": 328, + "y": 828, + "width": 180, + "height": 14, + "text": "Revoke all other sessions", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sessions-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/high-fidelity/sidebar-admin.excalidraw b/.context/turbostarter-framework-context/wireframes/high-fidelity/sidebar-admin.excalidraw new file mode 100644 index 0000000..58e709e --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/high-fidelity/sidebar-admin.excalidraw @@ -0,0 +1,1039 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "sidebar-container", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-area", + "type": "rectangle", + "x": 12, + "y": 16, + "width": 256, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-hexagon-outer", + "type": "diamond", + "x": 24, + "y": 22, + "width": 36, + "height": 36, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": { "type": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-hexagon-inner", + "type": "diamond", + "x": 32, + "y": 30, + "width": 20, + "height": 20, + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": { "type": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 68, + "y": 30, + "width": 80, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "MCPGet", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 14 + }, + { + "id": "admin-badge-bg", + "type": "rectangle", + "x": 158, + "y": 28, + "width": 56, + "height": 24, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "admin-badge-text", + "type": "text", + "x": 168, + "y": 32, + "width": 36, + "height": 16, + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Admin", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 10 + }, + { + "id": "section-divider-1", + "type": "line", + "x": 12, + "y": 76, + "width": 256, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [256, 0]] + }, + { + "id": "section-label-admin-text", + "type": "text", + "x": 24, + "y": 88, + "width": 50, + "height": 14, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "section-admin"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "ADMIN", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "nav-home-active", + "type": "rectangle", + "x": 12, + "y": 108, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-home-icon-roof", + "type": "line", + "x": 24, + "y": 118, + "width": 20, + "height": 10, + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 10], [10, 0], [20, 10]] + }, + { + "id": "nav-home-icon-body", + "type": "rectangle", + "x": 27, + "y": 126, + "width": 14, + "height": 12, + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-home-icon-door", + "type": "rectangle", + "x": 31, + "y": 130, + "width": 6, + "height": 8, + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-home-text", + "type": "text", + "x": 56, + "y": 118, + "width": 60, + "height": 20, + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Home", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-users-hover", + "type": "rectangle", + "x": 12, + "y": 156, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-users-icon-person1", + "type": "ellipse", + "x": 24, + "y": 164, + "width": 10, + "height": 10, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-users-icon-body1", + "type": "line", + "x": 21, + "y": 176, + "width": 16, + "height": 10, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": { "type": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 10], [8, 0], [16, 10]] + }, + { + "id": "nav-users-icon-person2", + "type": "ellipse", + "x": 34, + "y": 166, + "width": 8, + "height": 8, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 70, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-users-icon-body2", + "type": "line", + "x": 32, + "y": 176, + "width": 12, + "height": 8, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 70, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": { "type": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 8], [6, 0], [12, 8]] + }, + { + "id": "nav-users-text", + "type": "text", + "x": 56, + "y": 166, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Users", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-users-badge-bg", + "type": "rectangle", + "x": 216, + "y": 168, + "width": 40, + "height": 18, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": { "type": 3, "value": 9 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-users-badge-text", + "type": "text", + "x": 224, + "y": 170, + "width": 24, + "height": 14, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "1.2k", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "nav-orgs", + "type": "rectangle", + "x": 12, + "y": 204, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-orgs-icon-building-main", + "type": "rectangle", + "x": 26, + "y": 214, + "width": 16, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-orgs-icon-window1", + "type": "rectangle", + "x": 29, + "y": 218, + "width": 4, + "height": 4, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "$sidebar-foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-orgs-icon-window2", + "type": "rectangle", + "x": 35, + "y": 218, + "width": 4, + "height": 4, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "$sidebar-foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-orgs-icon-window3", + "type": "rectangle", + "x": 29, + "y": 225, + "width": 4, + "height": 4, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "$sidebar-foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-orgs-icon-window4", + "type": "rectangle", + "x": 35, + "y": 225, + "width": 4, + "height": 4, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "$sidebar-foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-orgs-text", + "type": "text", + "x": 56, + "y": 214, + "width": 100, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Organizations", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-customers", + "type": "rectangle", + "x": 12, + "y": 252, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-customers-icon-hand", + "type": "line", + "x": 24, + "y": 270, + "width": 18, + "height": 12, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": { "type": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 12], [9, 0], [18, 12]] + }, + { + "id": "nav-customers-icon-coin", + "type": "ellipse", + "x": 28, + "y": 262, + "width": 12, + "height": 12, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-customers-icon-dollar", + "type": "text", + "x": 31, + "y": 263, + "width": 6, + "height": 10, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "$", + "fontSize": 9, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 7 + }, + { + "id": "nav-customers-text", + "type": "text", + "x": 56, + "y": 262, + "width": 80, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Customers", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-2", + "type": "line", + "x": 12, + "y": 308, + "width": 256, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [256, 0]] + }, + { + "id": "nav-back", + "type": "rectangle", + "x": 12, + "y": 320, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-back"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-back-arrow-line", + "type": "line", + "x": 24, + "y": 340, + "width": 16, + "height": 0, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-back"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [16, 0]] + }, + { + "id": "nav-back-arrow-head", + "type": "line", + "x": 24, + "y": 334, + "width": 6, + "height": 12, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-back"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[6, 0], [0, 6], [6, 12]] + }, + { + "id": "nav-back-text", + "type": "text", + "x": 56, + "y": 330, + "width": 140, + "height": 20, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-back"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Back to Dashboard", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-footer", + "type": "line", + "x": 12, + "y": 812, + "width": 256, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [256, 0]] + }, + { + "id": "user-footer", + "type": "rectangle", + "x": 12, + "y": 824, + "width": 256, + "height": 64, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-avatar", + "type": "ellipse", + "x": 24, + "y": 836, + "width": 40, + "height": 40, + "strokeColor": "$destructive", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-avatar-initials", + "type": "text", + "x": 32, + "y": 848, + "width": 24, + "height": 16, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "AU", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 10 + }, + { + "id": "user-name", + "type": "text", + "x": 76, + "y": 838, + "width": 100, + "height": 16, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Admin User", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 12 + }, + { + "id": "role-badge-bg", + "type": "rectangle", + "x": 76, + "y": 858, + "width": 50, + "height": 18, + "strokeColor": "transparent", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["sidebar-group", "user-group"], + "roundness": { "type": 3, "value": 9 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "role-badge-text", + "type": "text", + "x": 84, + "y": 860, + "width": 34, + "height": 14, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "admin", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "dropdown-indicator", + "type": "line", + "x": 240, + "y": 850, + "width": 12, + "height": 8, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [6, 8], [12, 0]] + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/high-fidelity/sidebar-apps.excalidraw b/.context/turbostarter-framework-context/wireframes/high-fidelity/sidebar-apps.excalidraw new file mode 100644 index 0000000..2894d63 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/high-fidelity/sidebar-apps.excalidraw @@ -0,0 +1,1651 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "sidebar-container", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-area", + "type": "rectangle", + "x": 12, + "y": 16, + "width": 256, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-hexagon-outer", + "type": "diamond", + "x": 24, + "y": 22, + "width": 36, + "height": 36, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": { "type": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-hexagon-inner", + "type": "diamond", + "x": 32, + "y": 30, + "width": 20, + "height": 20, + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": { "type": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 68, + "y": 30, + "width": 80, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "MCPGet", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 14 + }, + { + "id": "collapse-button", + "type": "rectangle", + "x": 236, + "y": 24, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "collapse-chevron", + "type": "line", + "x": 248, + "y": 35, + "width": 8, + "height": 10, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[8, 0], [0, 5], [8, 10]] + }, + { + "id": "section-divider-1", + "type": "line", + "x": 12, + "y": 76, + "width": 256, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [256, 0]] + }, + { + "id": "section-label-apps-text", + "type": "text", + "x": 24, + "y": 88, + "width": 40, + "height": 14, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "section-apps"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "APPS", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "nav-chat-active", + "type": "rectangle", + "x": 12, + "y": 108, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-chat-icon-bubble", + "type": "rectangle", + "x": 24, + "y": 118, + "width": 20, + "height": 16, + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-chat-icon-tail", + "type": "line", + "x": 24, + "y": 130, + "width": 6, + "height": 6, + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [0, 6], [6, 0]] + }, + { + "id": "nav-chat-text", + "type": "text", + "x": 56, + "y": 118, + "width": 60, + "height": 20, + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Chat", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-chat-badge-bg", + "type": "rectangle", + "x": 228, + "y": 118, + "width": 28, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-chat-badge-text", + "type": "text", + "x": 236, + "y": 120, + "width": 12, + "height": 16, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "3", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 10 + }, + { + "id": "nav-image-hover", + "type": "rectangle", + "x": 12, + "y": 156, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-image-icon-frame", + "type": "rectangle", + "x": 24, + "y": 166, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-image-icon-mountain", + "type": "line", + "x": 27, + "y": 180, + "width": 14, + "height": 8, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 8], [5, 0], [9, 4], [14, 8]] + }, + { + "id": "nav-image-icon-sun", + "type": "ellipse", + "x": 36, + "y": 169, + "width": 5, + "height": 5, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "$sidebar-foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-image-text", + "type": "text", + "x": 56, + "y": 166, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Image", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-tts", + "type": "rectangle", + "x": 12, + "y": 204, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-tts-icon-speaker", + "type": "rectangle", + "x": 24, + "y": 218, + "width": 8, + "height": 12, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": { "type": 3, "value": 1 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-tts-icon-wave1", + "type": "line", + "x": 34, + "y": 216, + "width": 0, + "height": 16, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": { "type": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 4], [4, 0], [4, 8], [0, 12]] + }, + { + "id": "nav-tts-icon-wave2", + "type": "line", + "x": 40, + "y": 214, + "width": 0, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 60, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": { "type": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 4], [4, 0], [4, 10], [0, 16]] + }, + { + "id": "nav-tts-text", + "type": "text", + "x": 56, + "y": 214, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "TTS", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-pdf", + "type": "rectangle", + "x": 12, + "y": 252, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-pdf-icon-doc", + "type": "rectangle", + "x": 24, + "y": 262, + "width": 16, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-pdf-icon-fold", + "type": "line", + "x": 34, + "y": 262, + "width": 6, + "height": 6, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 6], [0, 0], [6, 0]] + }, + { + "id": "nav-pdf-icon-line1", + "type": "line", + "x": 27, + "y": 272, + "width": 10, + "height": 0, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [10, 0]] + }, + { + "id": "nav-pdf-icon-line2", + "type": "line", + "x": 27, + "y": 276, + "width": 10, + "height": 0, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [10, 0]] + }, + { + "id": "nav-pdf-text", + "type": "text", + "x": 56, + "y": 262, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "PDF", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-agent", + "type": "rectangle", + "x": 12, + "y": 300, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-agent-icon-sparkle-main", + "type": "diamond", + "x": 28, + "y": 314, + "width": 12, + "height": 12, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "$sidebar-foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-agent-icon-sparkle-small", + "type": "diamond", + "x": 38, + "y": 310, + "width": 6, + "height": 6, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "$sidebar-foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-agent-text", + "type": "text", + "x": 56, + "y": 310, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Agent", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-2", + "type": "line", + "x": 12, + "y": 356, + "width": 256, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [256, 0]] + }, + { + "id": "section-label-free-tools-text", + "type": "text", + "x": 24, + "y": 368, + "width": 80, + "height": 14, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "section-free-tools"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "FREE TOOLS", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "nav-envin", + "type": "rectangle", + "x": 12, + "y": 388, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-envin-icon", + "type": "rectangle", + "x": 24, + "y": 398, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-envin-text", + "type": "text", + "x": 56, + "y": 398, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Envin", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-ideas", + "type": "rectangle", + "x": 12, + "y": 436, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-ideas-icon", + "type": "ellipse", + "x": 26, + "y": 446, + "width": 16, + "height": 16, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-ideas-icon-filament", + "type": "line", + "x": 31, + "y": 462, + "width": 6, + "height": 4, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [3, 4], [6, 0]] + }, + { + "id": "nav-ideas-text", + "type": "text", + "x": 56, + "y": 446, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Ideas", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-extro", + "type": "rectangle", + "x": 12, + "y": 484, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-extro-icon", + "type": "rectangle", + "x": 24, + "y": 494, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-extro-text", + "type": "text", + "x": 56, + "y": 494, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Extro", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-emojai", + "type": "rectangle", + "x": 12, + "y": 532, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-emojai-icon", + "type": "ellipse", + "x": 24, + "y": 542, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-emojai-icon-smile", + "type": "line", + "x": 29, + "y": 554, + "width": 10, + "height": 4, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": { "type": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [5, 4], [10, 0]] + }, + { + "id": "nav-emojai-text", + "type": "text", + "x": 56, + "y": 542, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Emojai", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-3", + "type": "line", + "x": 12, + "y": 588, + "width": 256, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [256, 0]] + }, + { + "id": "section-label-other-text", + "type": "text", + "x": 24, + "y": 600, + "width": 50, + "height": 14, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "section-other"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "OTHER", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "nav-home", + "type": "rectangle", + "x": 12, + "y": 620, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-other"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-home-icon-house", + "type": "line", + "x": 24, + "y": 630, + "width": 20, + "height": 10, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-other"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 10], [10, 0], [20, 10]] + }, + { + "id": "nav-home-icon-body", + "type": "rectangle", + "x": 27, + "y": 638, + "width": 14, + "height": 12, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-other"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-home-text", + "type": "text", + "x": 56, + "y": 630, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-other"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Home", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-docs", + "type": "rectangle", + "x": 12, + "y": 668, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-other"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-docs-icon", + "type": "rectangle", + "x": 24, + "y": 678, + "width": 16, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-other"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-docs-icon-line1", + "type": "line", + "x": 27, + "y": 684, + "width": 10, + "height": 0, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-other"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [10, 0]] + }, + { + "id": "nav-docs-icon-line2", + "type": "line", + "x": 27, + "y": 689, + "width": 10, + "height": 0, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-other"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [10, 0]] + }, + { + "id": "nav-docs-icon-line3", + "type": "line", + "x": 27, + "y": 694, + "width": 6, + "height": 0, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-other"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [6, 0]] + }, + { + "id": "nav-docs-text", + "type": "text", + "x": 56, + "y": 678, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-other"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Docs", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-blog", + "type": "rectangle", + "x": 12, + "y": 716, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-other"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-blog-icon", + "type": "rectangle", + "x": 24, + "y": 726, + "width": 20, + "height": 16, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-other"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-blog-icon-pen", + "type": "line", + "x": 36, + "y": 736, + "width": 10, + "height": 10, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-other"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 10], [10, 0]] + }, + { + "id": "nav-blog-text", + "type": "text", + "x": 56, + "y": 726, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-other"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Blog", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-footer", + "type": "line", + "x": 12, + "y": 812, + "width": 256, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [256, 0]] + }, + { + "id": "user-footer", + "type": "rectangle", + "x": 12, + "y": 824, + "width": 256, + "height": 64, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-avatar", + "type": "ellipse", + "x": 24, + "y": 836, + "width": 40, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-avatar-initials", + "type": "text", + "x": 32, + "y": 848, + "width": 24, + "height": 16, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "JD", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 10 + }, + { + "id": "user-status-dot", + "type": "ellipse", + "x": 54, + "y": 866, + "width": 12, + "height": 12, + "strokeColor": "$sidebar", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-name", + "type": "text", + "x": 76, + "y": 838, + "width": 120, + "height": 16, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "John Doe", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 12 + }, + { + "id": "user-email", + "type": "text", + "x": 76, + "y": 858, + "width": 140, + "height": 14, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "john@example.com", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 10 + }, + { + "id": "dropdown-indicator", + "type": "line", + "x": 240, + "y": 850, + "width": 12, + "height": 8, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [6, 8], [12, 0]] + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/high-fidelity/sidebar-dashboard.excalidraw b/.context/turbostarter-framework-context/wireframes/high-fidelity/sidebar-dashboard.excalidraw new file mode 100644 index 0000000..e4e6a16 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/high-fidelity/sidebar-dashboard.excalidraw @@ -0,0 +1,1179 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "sidebar-container", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-area", + "type": "rectangle", + "x": 12, + "y": 16, + "width": 256, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-hexagon-outer", + "type": "diamond", + "x": 24, + "y": 22, + "width": 36, + "height": 36, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": { "type": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-hexagon-inner", + "type": "diamond", + "x": 32, + "y": 30, + "width": 20, + "height": 20, + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": { "type": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 68, + "y": 30, + "width": 80, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "MCPGet", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 14 + }, + { + "id": "section-divider-1", + "type": "line", + "x": 12, + "y": 76, + "width": 256, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [256, 0]] + }, + { + "id": "section-label-platform-text", + "type": "text", + "x": 24, + "y": 88, + "width": 70, + "height": 14, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "section-platform"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "PLATFORM", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "nav-dashboard-active", + "type": "rectangle", + "x": 12, + "y": 108, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-platform"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-dashboard-icon-grid-tl", + "type": "rectangle", + "x": 24, + "y": 118, + "width": 8, + "height": 8, + "strokeColor": "$card", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-platform"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-dashboard-icon-grid-tr", + "type": "rectangle", + "x": 34, + "y": 118, + "width": 8, + "height": 8, + "strokeColor": "$card", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-platform"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-dashboard-icon-grid-bl", + "type": "rectangle", + "x": 24, + "y": 128, + "width": 8, + "height": 8, + "strokeColor": "$card", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-platform"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-dashboard-icon-grid-br", + "type": "rectangle", + "x": 34, + "y": 128, + "width": 8, + "height": 8, + "strokeColor": "$card", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-platform"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-dashboard-text", + "type": "text", + "x": 56, + "y": 118, + "width": 80, + "height": 20, + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-platform"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Dashboard", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-ai-tools", + "type": "rectangle", + "x": 12, + "y": 156, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-platform"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-ai-tools-icon-sparkle-main", + "type": "diamond", + "x": 28, + "y": 170, + "width": 12, + "height": 12, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "$sidebar-foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-platform"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-ai-tools-icon-sparkle-small", + "type": "diamond", + "x": 38, + "y": 166, + "width": 6, + "height": 6, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "$sidebar-foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["sidebar-group", "nav-platform"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-ai-tools-text", + "type": "text", + "x": 56, + "y": 166, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-platform"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "AI Tools", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-2", + "type": "line", + "x": 12, + "y": 212, + "width": 256, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [256, 0]] + }, + { + "id": "section-label-manage-text", + "type": "text", + "x": 24, + "y": 224, + "width": 60, + "height": 14, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "section-manage"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "MANAGE", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "nav-settings-hover", + "type": "rectangle", + "x": 12, + "y": 244, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["sidebar-group", "nav-manage"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-settings-icon-gear-outer", + "type": "ellipse", + "x": 24, + "y": 254, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-manage"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-settings-icon-gear-inner", + "type": "ellipse", + "x": 30, + "y": 260, + "width": 8, + "height": 8, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-manage"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-settings-icon-tooth-top", + "type": "rectangle", + "x": 32, + "y": 250, + "width": 4, + "height": 4, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "$sidebar-foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-manage"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-settings-icon-tooth-bottom", + "type": "rectangle", + "x": 32, + "y": 274, + "width": 4, + "height": 4, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "$sidebar-foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-manage"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-settings-icon-tooth-left", + "type": "rectangle", + "x": 20, + "y": 262, + "width": 4, + "height": 4, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "$sidebar-foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-manage"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-settings-icon-tooth-right", + "type": "rectangle", + "x": 44, + "y": 262, + "width": 4, + "height": 4, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "$sidebar-foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-manage"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-settings-text", + "type": "text", + "x": 56, + "y": 254, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-manage"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Settings", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-settings-badge-bg", + "type": "rectangle", + "x": 220, + "y": 256, + "width": 36, + "height": 18, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["sidebar-group", "nav-manage"], + "roundness": { "type": 3, "value": 9 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-settings-badge-text", + "type": "text", + "x": 226, + "y": 258, + "width": 24, + "height": 14, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-manage"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "New", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "section-divider-3", + "type": "line", + "x": 12, + "y": 300, + "width": 256, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [256, 0]] + }, + { + "id": "section-label-dev-text", + "type": "text", + "x": 24, + "y": 312, + "width": 30, + "height": 14, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "section-dev"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "DEV", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "nav-demos", + "type": "rectangle", + "x": 12, + "y": 332, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-dev"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-demos-icon-layout-main", + "type": "rectangle", + "x": 24, + "y": 342, + "width": 10, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-dev"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-demos-icon-layout-top", + "type": "rectangle", + "x": 36, + "y": 342, + "width": 10, + "height": 8, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-dev"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-demos-icon-layout-bottom", + "type": "rectangle", + "x": 36, + "y": 354, + "width": 10, + "height": 8, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-dev"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-demos-text", + "type": "text", + "x": 56, + "y": 342, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-dev"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Demos", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-bottom", + "type": "line", + "x": 12, + "y": 700, + "width": 256, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [256, 0]] + }, + { + "id": "nav-support", + "type": "rectangle", + "x": 12, + "y": 712, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-bottom"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-support-icon-ring-outer", + "type": "ellipse", + "x": 24, + "y": 722, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-bottom"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-support-icon-ring-inner", + "type": "ellipse", + "x": 30, + "y": 728, + "width": 8, + "height": 8, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-bottom"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-support-icon-rope1", + "type": "line", + "x": 26, + "y": 724, + "width": 6, + "height": 6, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-bottom"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 6], [6, 0]] + }, + { + "id": "nav-support-icon-rope2", + "type": "line", + "x": 36, + "y": 734, + "width": 6, + "height": 6, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-bottom"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 6], [6, 0]] + }, + { + "id": "nav-support-text", + "type": "text", + "x": 56, + "y": 722, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-bottom"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Support", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-feedback", + "type": "rectangle", + "x": 12, + "y": 760, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-bottom"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-feedback-icon-bubble", + "type": "rectangle", + "x": 24, + "y": 770, + "width": 20, + "height": 16, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-bottom"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-feedback-icon-tail", + "type": "line", + "x": 24, + "y": 782, + "width": 6, + "height": 6, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-bottom"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [0, 6], [6, 0]] + }, + { + "id": "nav-feedback-icon-dots", + "type": "line", + "x": 28, + "y": 777, + "width": 12, + "height": 0, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-bottom"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [4, 0], [8, 0], [12, 0]] + }, + { + "id": "nav-feedback-text", + "type": "text", + "x": 56, + "y": 770, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-bottom"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Feedback", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-footer", + "type": "line", + "x": 12, + "y": 812, + "width": 256, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [256, 0]] + }, + { + "id": "user-footer", + "type": "rectangle", + "x": 12, + "y": 824, + "width": 256, + "height": 64, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-avatar", + "type": "ellipse", + "x": 24, + "y": 840, + "width": 40, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-avatar-initials", + "type": "text", + "x": 32, + "y": 852, + "width": 24, + "height": 16, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "JD", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 10 + }, + { + "id": "user-name", + "type": "text", + "x": 76, + "y": 852, + "width": 120, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "John Doe", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "dropdown-indicator", + "type": "line", + "x": 240, + "y": 856, + "width": 12, + "height": 8, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [6, 8], [12, 0]] + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/low-fidelity/auth-forgot-password.excalidraw b/.context/turbostarter-framework-context/wireframes/low-fidelity/auth-forgot-password.excalidraw new file mode 100644 index 0000000..627de5a --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/low-fidelity/auth-forgot-password.excalidraw @@ -0,0 +1,231 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "left-column", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["left-column-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-column", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-column-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-placeholder", + "type": "rectangle", + "x": 300, + "y": 200, + "width": 120, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["left-column-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "forgot-password-title", + "type": "rectangle", + "x": 200, + "y": 300, + "width": 320, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "description-text", + "type": "rectangle", + "x": 200, + "y": 350, + "width": 320, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-input", + "type": "rectangle", + "x": 200, + "y": 420, + "width": 320, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "send-reset-button", + "type": "rectangle", + "x": 200, + "y": 500, + "width": 320, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "back-to-login-link", + "type": "rectangle", + "x": 280, + "y": 580, + "width": 160, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo", + "type": "rectangle", + "x": 1000, + "y": 400, + "width": 160, + "height": 60, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-column-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-tagline", + "type": "rectangle", + "x": 920, + "y": 480, + "width": 320, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-column-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/low-fidelity/auth-join-org.excalidraw b/.context/turbostarter-framework-context/wireframes/low-fidelity/auth-join-org.excalidraw new file mode 100644 index 0000000..8bc900e --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/low-fidelity/auth-join-org.excalidraw @@ -0,0 +1,311 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "left-column", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["left-column-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-column", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-column-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-placeholder", + "type": "rectangle", + "x": 300, + "y": 120, + "width": 120, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["left-column-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "join-org-title", + "type": "rectangle", + "x": 200, + "y": 200, + "width": 320, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "invitation-card", + "type": "rectangle", + "x": 180, + "y": 280, + "width": 360, + "height": 200, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["invitation-card-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "org-logo", + "type": "rectangle", + "x": 320, + "y": 300, + "width": 80, + "height": 80, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["invitation-card-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "org-name", + "type": "rectangle", + "x": 260, + "y": 400, + "width": 200, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["invitation-card-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "inviter-name", + "type": "rectangle", + "x": 280, + "y": 434, + "width": 160, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["invitation-card-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "role-badge", + "type": "rectangle", + "x": 320, + "y": 458, + "width": 80, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["invitation-card-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "accept-button", + "type": "rectangle", + "x": 200, + "y": 520, + "width": 320, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "decline-link", + "type": "rectangle", + "x": 320, + "y": 590, + "width": 80, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "wrong-account-link", + "type": "rectangle", + "x": 260, + "y": 640, + "width": 200, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo", + "type": "rectangle", + "x": 1000, + "y": 400, + "width": 160, + "height": 60, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-column-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-tagline", + "type": "rectangle", + "x": 920, + "y": 480, + "width": 320, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-column-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/low-fidelity/auth-login.excalidraw b/.context/turbostarter-framework-context/wireframes/low-fidelity/auth-login.excalidraw new file mode 100644 index 0000000..31216f2 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/low-fidelity/auth-login.excalidraw @@ -0,0 +1,351 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "left-column", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["left-column-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-column", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-column-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-placeholder", + "type": "rectangle", + "x": 300, + "y": 80, + "width": 120, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["left-column-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-title", + "type": "rectangle", + "x": 200, + "y": 180, + "width": 320, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-input", + "type": "rectangle", + "x": 200, + "y": 240, + "width": 320, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-input", + "type": "rectangle", + "x": 200, + "y": 300, + "width": 320, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "forgot-password-link", + "type": "rectangle", + "x": 400, + "y": 360, + "width": 120, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "login-button", + "type": "rectangle", + "x": 200, + "y": 400, + "width": 320, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-line", + "type": "rectangle", + "x": 200, + "y": 474, + "width": 320, + "height": 2, + "strokeColor": "$border", + "backgroundColor": "$border", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-text", + "type": "rectangle", + "x": 300, + "y": 464, + "width": 120, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-google", + "type": "rectangle", + "x": 200, + "y": 520, + "width": 100, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-github", + "type": "rectangle", + "x": 310, + "y": 520, + "width": 100, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-apple", + "type": "rectangle", + "x": 420, + "y": 520, + "width": 100, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "signup-link", + "type": "rectangle", + "x": 240, + "y": 600, + "width": 240, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo", + "type": "rectangle", + "x": 1000, + "y": 400, + "width": 160, + "height": 60, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-column-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-tagline", + "type": "rectangle", + "x": 920, + "y": 480, + "width": 320, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-column-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/low-fidelity/auth-register.excalidraw b/.context/turbostarter-framework-context/wireframes/low-fidelity/auth-register.excalidraw new file mode 100644 index 0000000..8da6d99 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/low-fidelity/auth-register.excalidraw @@ -0,0 +1,411 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "left-column", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["left-column-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-column", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-column-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-placeholder", + "type": "rectangle", + "x": 300, + "y": 60, + "width": 120, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["left-column-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "create-account-title", + "type": "rectangle", + "x": 200, + "y": 120, + "width": 320, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-input", + "type": "rectangle", + "x": 200, + "y": 180, + "width": 320, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-input", + "type": "rectangle", + "x": 200, + "y": 240, + "width": 320, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-input", + "type": "rectangle", + "x": 200, + "y": 300, + "width": 320, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "confirm-password-input", + "type": "rectangle", + "x": 200, + "y": 360, + "width": 320, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "terms-checkbox", + "type": "rectangle", + "x": 200, + "y": 420, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["terms-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "terms-text", + "type": "rectangle", + "x": 230, + "y": 420, + "width": 290, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["terms-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "create-account-button", + "type": "rectangle", + "x": 200, + "y": 460, + "width": 320, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-line", + "type": "rectangle", + "x": 200, + "y": 534, + "width": 320, + "height": 2, + "strokeColor": "$border", + "backgroundColor": "$border", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-text", + "type": "rectangle", + "x": 300, + "y": 524, + "width": 120, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-google", + "type": "rectangle", + "x": 200, + "y": 580, + "width": 100, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-github", + "type": "rectangle", + "x": 310, + "y": 580, + "width": 100, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-apple", + "type": "rectangle", + "x": 420, + "y": 580, + "width": 100, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "login-link", + "type": "rectangle", + "x": 240, + "y": 660, + "width": 240, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo", + "type": "rectangle", + "x": 1000, + "y": 400, + "width": 160, + "height": 60, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-column-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-tagline", + "type": "rectangle", + "x": 920, + "y": 480, + "width": 320, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-column-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/low-fidelity/dashboard-admin.excalidraw b/.context/turbostarter-framework-context/wireframes/low-fidelity/dashboard-admin.excalidraw new file mode 100644 index 0000000..83629ca --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/low-fidelity/dashboard-admin.excalidraw @@ -0,0 +1,1451 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-home", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-home-icon", + "type": "rectangle", + "x": 32, + "y": 110, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-home-text", + "type": "rectangle", + "x": 64, + "y": 112, + "width": 50, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-icon", + "type": "rectangle", + "x": 32, + "y": 170, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-text", + "type": "rectangle", + "x": 64, + "y": 172, + "width": 50, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-orgs", + "type": "rectangle", + "x": 20, + "y": 220, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-orgs-icon", + "type": "rectangle", + "x": 32, + "y": 230, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-orgs-text", + "type": "rectangle", + "x": 64, + "y": 232, + "width": 100, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-customers", + "type": "rectangle", + "x": 20, + "y": 280, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-customers-icon", + "type": "rectangle", + "x": 32, + "y": 290, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-customers-text", + "type": "rectangle", + "x": 64, + "y": 292, + "width": 80, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "rectangle", + "x": 304, + "y": 20, + "width": 160, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-user-avatar", + "type": "rectangle", + "x": 1388, + "y": 16, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 16 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 264, + "height": 100, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-label", + "type": "rectangle", + "x": 324, + "y": 108, + "width": 80, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-value", + "type": "rectangle", + "x": 324, + "y": 136, + "width": 100, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2", + "type": "rectangle", + "x": 584, + "y": 88, + "width": 264, + "height": 100, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-label", + "type": "rectangle", + "x": 604, + "y": 108, + "width": 80, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-value", + "type": "rectangle", + "x": 604, + "y": 136, + "width": 60, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3", + "type": "rectangle", + "x": 864, + "y": 88, + "width": 264, + "height": 100, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-label", + "type": "rectangle", + "x": 884, + "y": 108, + "width": 60, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-value", + "type": "rectangle", + "x": 884, + "y": 136, + "width": 100, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4", + "type": "rectangle", + "x": 1144, + "y": 88, + "width": 264, + "height": 100, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-label", + "type": "rectangle", + "x": 1164, + "y": 108, + "width": 100, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-value", + "type": "rectangle", + "x": 1164, + "y": 136, + "width": 80, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-card", + "type": "rectangle", + "x": 304, + "y": 208, + "width": 720, + "height": 400, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-card-title", + "type": "rectangle", + "x": 324, + "y": 228, + "width": 120, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-1", + "type": "rectangle", + "x": 324, + "y": 268, + "width": 680, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-1-avatar", + "type": "rectangle", + "x": 340, + "y": 280, + "width": 24, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-1-text", + "type": "rectangle", + "x": 376, + "y": 284, + "width": 300, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-1-time", + "type": "rectangle", + "x": 924, + "y": 284, + "width": 60, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-2", + "type": "rectangle", + "x": 324, + "y": 328, + "width": 680, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-2-avatar", + "type": "rectangle", + "x": 340, + "y": 340, + "width": 24, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-2-text", + "type": "rectangle", + "x": 376, + "y": 344, + "width": 280, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-2-time", + "type": "rectangle", + "x": 924, + "y": 344, + "width": 60, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-3", + "type": "rectangle", + "x": 324, + "y": 388, + "width": 680, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-3-avatar", + "type": "rectangle", + "x": 340, + "y": 400, + "width": 24, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-3-text", + "type": "rectangle", + "x": 376, + "y": 404, + "width": 320, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-3-time", + "type": "rectangle", + "x": 924, + "y": 404, + "width": 60, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-4", + "type": "rectangle", + "x": 324, + "y": 448, + "width": 680, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-4-avatar", + "type": "rectangle", + "x": 340, + "y": 460, + "width": 24, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-4-text", + "type": "rectangle", + "x": 376, + "y": 464, + "width": 260, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-4-time", + "type": "rectangle", + "x": 924, + "y": 464, + "width": 60, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-5", + "type": "rectangle", + "x": 324, + "y": 508, + "width": 680, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-5-avatar", + "type": "rectangle", + "x": 340, + "y": 520, + "width": 24, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-5-text", + "type": "rectangle", + "x": 376, + "y": 524, + "width": 340, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-5-time", + "type": "rectangle", + "x": 924, + "y": 524, + "width": 60, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-6", + "type": "rectangle", + "x": 324, + "y": 568, + "width": 680, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-6-avatar", + "type": "rectangle", + "x": 340, + "y": 580, + "width": 24, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-6-text", + "type": "rectangle", + "x": 376, + "y": 584, + "width": 290, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-6-time", + "type": "rectangle", + "x": 924, + "y": 584, + "width": 60, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-actions-card", + "type": "rectangle", + "x": 1044, + "y": 208, + "width": 364, + "height": 400, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-actions-title", + "type": "rectangle", + "x": 1064, + "y": 228, + "width": 120, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-1", + "type": "rectangle", + "x": 1064, + "y": 268, + "width": 324, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-1-text", + "type": "rectangle", + "x": 1144, + "y": 284, + "width": 160, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-2", + "type": "rectangle", + "x": 1064, + "y": 332, + "width": 324, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-2-text", + "type": "rectangle", + "x": 1144, + "y": 348, + "width": 140, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-3", + "type": "rectangle", + "x": 1064, + "y": 396, + "width": 324, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-3-text", + "type": "rectangle", + "x": 1144, + "y": 412, + "width": 120, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-4", + "type": "rectangle", + "x": 1064, + "y": 460, + "width": 324, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-4-text", + "type": "rectangle", + "x": 1144, + "y": 476, + "width": 100, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-5", + "type": "rectangle", + "x": 1064, + "y": 524, + "width": 324, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-5-text", + "type": "rectangle", + "x": 1144, + "y": 540, + "width": 140, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/low-fidelity/dashboard-org.excalidraw b/.context/turbostarter-framework-context/wireframes/low-fidelity/dashboard-org.excalidraw new file mode 100644 index 0000000..68b5326 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/low-fidelity/dashboard-org.excalidraw @@ -0,0 +1,1111 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-overview", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-overview-icon", + "type": "rectangle", + "x": 32, + "y": 110, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-overview-text", + "type": "rectangle", + "x": 64, + "y": 112, + "width": 80, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-icon", + "type": "rectangle", + "x": 32, + "y": 170, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-text", + "type": "rectangle", + "x": 64, + "y": 172, + "width": 70, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings", + "type": "rectangle", + "x": 20, + "y": 220, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-icon", + "type": "rectangle", + "x": 32, + "y": 230, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-text", + "type": "rectangle", + "x": 64, + "y": 232, + "width": 60, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing", + "type": "rectangle", + "x": 20, + "y": 280, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing-icon", + "type": "rectangle", + "x": 32, + "y": 290, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing-text", + "type": "rectangle", + "x": 64, + "y": 292, + "width": 50, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "rectangle", + "x": 304, + "y": 20, + "width": 200, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-user-avatar", + "type": "rectangle", + "x": 1388, + "y": 16, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 16 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 264, + "height": 100, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-label", + "type": "rectangle", + "x": 324, + "y": 108, + "width": 100, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-value", + "type": "rectangle", + "x": 324, + "y": 136, + "width": 80, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2", + "type": "rectangle", + "x": 584, + "y": 88, + "width": 264, + "height": 100, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-label", + "type": "rectangle", + "x": 604, + "y": 108, + "width": 100, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-value", + "type": "rectangle", + "x": 604, + "y": 136, + "width": 60, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3", + "type": "rectangle", + "x": 864, + "y": 88, + "width": 264, + "height": 100, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-label", + "type": "rectangle", + "x": 884, + "y": 108, + "width": 80, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-value", + "type": "rectangle", + "x": 884, + "y": 136, + "width": 100, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4", + "type": "rectangle", + "x": 1144, + "y": 88, + "width": 264, + "height": 100, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-label", + "type": "rectangle", + "x": 1164, + "y": 108, + "width": 100, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-value", + "type": "rectangle", + "x": 1164, + "y": 136, + "width": 80, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-card", + "type": "rectangle", + "x": 304, + "y": 208, + "width": 1104, + "height": 320, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-title", + "type": "rectangle", + "x": 324, + "y": 228, + "width": 120, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-area", + "type": "rectangle", + "x": 324, + "y": 268, + "width": 1064, + "height": 240, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-line-1", + "type": "rectangle", + "x": 324, + "y": 380, + "width": 1064, + "height": 4, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-line-2", + "type": "rectangle", + "x": 324, + "y": 340, + "width": 1064, + "height": 4, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-line-3", + "type": "rectangle", + "x": 324, + "y": 420, + "width": 1064, + "height": 4, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-line-4", + "type": "rectangle", + "x": 324, + "y": 460, + "width": 1064, + "height": 4, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1", + "type": "rectangle", + "x": 304, + "y": 548, + "width": 540, + "height": 240, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-title", + "type": "rectangle", + "x": 324, + "y": 568, + "width": 100, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-area", + "type": "rectangle", + "x": 324, + "y": 608, + "width": 500, + "height": 160, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-1", + "type": "rectangle", + "x": 364, + "y": 668, + "width": 40, + "height": 80, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-2", + "type": "rectangle", + "x": 444, + "y": 648, + "width": 40, + "height": 100, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-3", + "type": "rectangle", + "x": 524, + "y": 688, + "width": 40, + "height": 60, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-4", + "type": "rectangle", + "x": 604, + "y": 628, + "width": 40, + "height": 120, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-5", + "type": "rectangle", + "x": 684, + "y": 658, + "width": 40, + "height": 90, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-6", + "type": "rectangle", + "x": 764, + "y": 678, + "width": 40, + "height": 70, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2", + "type": "rectangle", + "x": 864, + "y": 548, + "width": 544, + "height": 240, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-title", + "type": "rectangle", + "x": 884, + "y": 568, + "width": 120, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-area", + "type": "rectangle", + "x": 884, + "y": 608, + "width": 504, + "height": 160, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-pie", + "type": "rectangle", + "x": 1036, + "y": 628, + "width": 120, + "height": 120, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 60 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-pie-inner", + "type": "rectangle", + "x": 1066, + "y": 658, + "width": 60, + "height": 60, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 30 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/low-fidelity/dashboard-user.excalidraw b/.context/turbostarter-framework-context/wireframes/low-fidelity/dashboard-user.excalidraw new file mode 100644 index 0000000..9b4c22a --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/low-fidelity/dashboard-user.excalidraw @@ -0,0 +1,891 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-icon", + "type": "rectangle", + "x": 32, + "y": 110, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-text", + "type": "rectangle", + "x": 64, + "y": 112, + "width": 80, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-aitools", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-aitools-icon", + "type": "rectangle", + "x": 32, + "y": 170, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-aitools-text", + "type": "rectangle", + "x": 64, + "y": 172, + "width": 60, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings", + "type": "rectangle", + "x": 20, + "y": 220, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-icon", + "type": "rectangle", + "x": 32, + "y": 230, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-text", + "type": "rectangle", + "x": 64, + "y": 232, + "width": 60, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-support-item", + "type": "rectangle", + "x": 20, + "y": 780, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-support-icon", + "type": "rectangle", + "x": 32, + "y": 790, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-support-text", + "type": "rectangle", + "x": 64, + "y": 792, + "width": 100, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "rectangle", + "x": 304, + "y": 20, + "width": 120, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-user-avatar", + "type": "rectangle", + "x": 1388, + "y": 16, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 16 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-card", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 1112, + "height": 120, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-title", + "type": "rectangle", + "x": 324, + "y": 108, + "width": 200, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-subtitle", + "type": "rectangle", + "x": 324, + "y": 144, + "width": 400, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-subtitle-2", + "type": "rectangle", + "x": 324, + "y": 168, + "width": 300, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1", + "type": "rectangle", + "x": 304, + "y": 228, + "width": 360, + "height": 200, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-icon", + "type": "rectangle", + "x": 324, + "y": 248, + "width": 48, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-title", + "type": "rectangle", + "x": 324, + "y": 312, + "width": 100, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-desc", + "type": "rectangle", + "x": 324, + "y": 344, + "width": 320, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-desc-2", + "type": "rectangle", + "x": 324, + "y": 368, + "width": 280, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-button", + "type": "rectangle", + "x": 324, + "y": 396, + "width": 100, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2", + "type": "rectangle", + "x": 680, + "y": 228, + "width": 360, + "height": 200, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-icon", + "type": "rectangle", + "x": 700, + "y": 248, + "width": 48, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-title", + "type": "rectangle", + "x": 700, + "y": 312, + "width": 140, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-desc", + "type": "rectangle", + "x": 700, + "y": 344, + "width": 320, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-desc-2", + "type": "rectangle", + "x": 700, + "y": 368, + "width": 260, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-button", + "type": "rectangle", + "x": 700, + "y": 396, + "width": 100, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3", + "type": "rectangle", + "x": 1056, + "y": 228, + "width": 360, + "height": 200, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-icon", + "type": "rectangle", + "x": 1076, + "y": 248, + "width": 48, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-title", + "type": "rectangle", + "x": 1076, + "y": 312, + "width": 100, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-desc", + "type": "rectangle", + "x": 1076, + "y": 344, + "width": 320, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-desc-2", + "type": "rectangle", + "x": 1076, + "y": 368, + "width": 280, + "height": 16, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-button", + "type": "rectangle", + "x": 1076, + "y": 396, + "width": 100, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/low-fidelity/data-table-invitations.excalidraw b/.context/turbostarter-framework-context/wireframes/low-fidelity/data-table-invitations.excalidraw new file mode 100644 index 0000000..404b65a --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/low-fidelity/data-table-invitations.excalidraw @@ -0,0 +1,1811 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-switcher", + "type": "rectangle", + "x": 20, + "y": 80, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-avatar", + "type": "rectangle", + "x": 32, + "y": 88, + "width": 24, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-name", + "type": "rectangle", + "x": 64, + "y": 92, + "width": 100, + "height": 16, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-section-label", + "type": "rectangle", + "x": 20, + "y": 140, + "width": 60, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-active", + "type": "rectangle", + "x": 20, + "y": 208, + "width": 240, + "height": 40, + "strokeColor": "$primary", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-icon", + "type": "rectangle", + "x": 32, + "y": 218, + "width": 20, + "height": 20, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-label", + "type": "rectangle", + "x": 60, + "y": 222, + "width": 60, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings", + "type": "rectangle", + "x": 20, + "y": 256, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing", + "type": "rectangle", + "x": 20, + "y": 304, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "rectangle", + "x": 304, + "y": 20, + "width": 100, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invite-button", + "type": "rectangle", + "x": 1296, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tabs-container", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 1112, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-members", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 120, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-members-label", + "type": "rectangle", + "x": 324, + "y": 104, + "width": 80, + "height": 16, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-invitations-active", + "type": "rectangle", + "x": 424, + "y": 88, + "width": 160, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-invitations-label", + "type": "rectangle", + "x": 444, + "y": 104, + "width": 120, + "height": 16, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-invitations-underline", + "type": "rectangle", + "x": 424, + "y": 132, + "width": 160, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-container", + "type": "rectangle", + "x": 304, + "y": 152, + "width": 1112, + "height": 716, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "toolbar", + "type": "rectangle", + "x": 304, + "y": 152, + "width": 1112, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-input", + "type": "rectangle", + "x": 316, + "y": 162, + "width": 240, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-status", + "type": "rectangle", + "x": 568, + "y": 162, + "width": 120, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-status-arrow", + "type": "rectangle", + "x": 668, + "y": 176, + "width": 8, + "height": 8, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-header-row", + "type": "rectangle", + "x": 304, + "y": 208, + "width": 1112, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-email-col", + "type": "rectangle", + "x": 320, + "y": 226, + "width": 50, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "header-row-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-role-col", + "type": "rectangle", + "x": 480, + "y": 226, + "width": 40, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "header-row-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invitedby-col", + "type": "rectangle", + "x": 620, + "y": 226, + "width": 70, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "header-row-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-sent-col", + "type": "rectangle", + "x": 780, + "y": 226, + "width": 40, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "header-row-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-expires-col", + "type": "rectangle", + "x": 900, + "y": 226, + "width": 50, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "header-row-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-status-col", + "type": "rectangle", + "x": 1040, + "y": 226, + "width": 50, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "header-row-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-actions-col", + "type": "rectangle", + "x": 1340, + "y": 226, + "width": 50, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "header-row-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-1", + "type": "rectangle", + "x": 304, + "y": 256, + "width": 1112, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-email", + "type": "rectangle", + "x": 320, + "y": 278, + "width": 140, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-badge", + "type": "rectangle", + "x": 480, + "y": 274, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-invitedby", + "type": "rectangle", + "x": 620, + "y": 278, + "width": 100, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-sent", + "type": "rectangle", + "x": 780, + "y": 278, + "width": 70, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-expires", + "type": "rectangle", + "x": 900, + "y": 278, + "width": 70, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status-badge", + "type": "rectangle", + "x": 1040, + "y": 274, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-resend", + "type": "rectangle", + "x": 1320, + "y": 274, + "width": 28, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-cancel", + "type": "rectangle", + "x": 1356, + "y": 274, + "width": 28, + "height": 20, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-2", + "type": "rectangle", + "x": 304, + "y": 312, + "width": 1112, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-email", + "type": "rectangle", + "x": 320, + "y": 334, + "width": 120, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-badge", + "type": "rectangle", + "x": 480, + "y": 330, + "width": 50, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-invitedby", + "type": "rectangle", + "x": 620, + "y": 334, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-sent", + "type": "rectangle", + "x": 780, + "y": 334, + "width": 70, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-expires", + "type": "rectangle", + "x": 900, + "y": 334, + "width": 70, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status-badge", + "type": "rectangle", + "x": 1040, + "y": 330, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-resend", + "type": "rectangle", + "x": 1320, + "y": 330, + "width": 28, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-cancel", + "type": "rectangle", + "x": 1356, + "y": 330, + "width": 28, + "height": 20, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-3", + "type": "rectangle", + "x": 304, + "y": 368, + "width": 1112, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-email", + "type": "rectangle", + "x": 320, + "y": 390, + "width": 150, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-badge", + "type": "rectangle", + "x": 480, + "y": 386, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-invitedby", + "type": "rectangle", + "x": 620, + "y": 390, + "width": 90, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-sent", + "type": "rectangle", + "x": 780, + "y": 390, + "width": 70, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-expires", + "type": "rectangle", + "x": 900, + "y": 390, + "width": 70, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-status-badge", + "type": "rectangle", + "x": 1040, + "y": 386, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-resend", + "type": "rectangle", + "x": 1320, + "y": 386, + "width": 28, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-cancel", + "type": "rectangle", + "x": 1356, + "y": 386, + "width": 28, + "height": 20, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-4", + "type": "rectangle", + "x": 304, + "y": 424, + "width": 1112, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-4-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-email", + "type": "rectangle", + "x": 320, + "y": 446, + "width": 130, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-4-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-role-badge", + "type": "rectangle", + "x": 480, + "y": 442, + "width": 50, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-4-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-invitedby", + "type": "rectangle", + "x": 620, + "y": 446, + "width": 110, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-4-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-sent", + "type": "rectangle", + "x": 780, + "y": 446, + "width": 70, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-4-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-expires", + "type": "rectangle", + "x": 900, + "y": 446, + "width": 70, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-4-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-status-badge", + "type": "rectangle", + "x": 1040, + "y": 442, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-4-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-action-resend", + "type": "rectangle", + "x": 1320, + "y": 442, + "width": 28, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-4-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-action-cancel", + "type": "rectangle", + "x": 1356, + "y": 442, + "width": 28, + "height": 20, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-4-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-5", + "type": "rectangle", + "x": 304, + "y": 480, + "width": 1112, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-5-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-email", + "type": "rectangle", + "x": 320, + "y": 502, + "width": 145, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-5-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-role-badge", + "type": "rectangle", + "x": 480, + "y": 498, + "width": 50, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-5-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-invitedby", + "type": "rectangle", + "x": 620, + "y": 502, + "width": 95, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-5-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-sent", + "type": "rectangle", + "x": 780, + "y": 502, + "width": 70, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-5-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-expires", + "type": "rectangle", + "x": 900, + "y": 502, + "width": 70, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-5-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-status-badge", + "type": "rectangle", + "x": 1040, + "y": 498, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-5-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-action-resend", + "type": "rectangle", + "x": 1320, + "y": 498, + "width": 28, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-5-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-action-cancel", + "type": "rectangle", + "x": 1356, + "y": 498, + "width": 28, + "height": 20, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-5-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "empty-rows-area", + "type": "rectangle", + "x": 304, + "y": 536, + "width": 1112, + "height": 284, + "strokeColor": "transparent", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-bar", + "type": "rectangle", + "x": 304, + "y": 820, + "width": 1112, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-info", + "type": "rectangle", + "x": 328, + "y": 836, + "width": 80, + "height": 16, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-prev", + "type": "rectangle", + "x": 1284, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-1", + "type": "rectangle", + "x": 1324, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-next", + "type": "rectangle", + "x": 1364, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/low-fidelity/data-table-members.excalidraw b/.context/turbostarter-framework-context/wireframes/low-fidelity/data-table-members.excalidraw new file mode 100644 index 0000000..b6c2696 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/low-fidelity/data-table-members.excalidraw @@ -0,0 +1,1831 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-switcher", + "type": "rectangle", + "x": 20, + "y": 80, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-avatar", + "type": "rectangle", + "x": 32, + "y": 88, + "width": 24, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-name", + "type": "rectangle", + "x": 64, + "y": 92, + "width": 100, + "height": 16, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-section-label", + "type": "rectangle", + "x": 20, + "y": 140, + "width": 60, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-active", + "type": "rectangle", + "x": 20, + "y": 208, + "width": 240, + "height": 40, + "strokeColor": "$primary", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-icon", + "type": "rectangle", + "x": 32, + "y": 218, + "width": 20, + "height": 20, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-label", + "type": "rectangle", + "x": 60, + "y": 222, + "width": 60, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings", + "type": "rectangle", + "x": 20, + "y": 256, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing", + "type": "rectangle", + "x": 20, + "y": 304, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "rectangle", + "x": 304, + "y": 20, + "width": 100, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invite-button", + "type": "rectangle", + "x": 1296, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tabs-container", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 1112, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-members-active", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 120, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-members-label", + "type": "rectangle", + "x": 324, + "y": 104, + "width": 80, + "height": 16, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-members-underline", + "type": "rectangle", + "x": 304, + "y": 132, + "width": 120, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-invitations", + "type": "rectangle", + "x": 424, + "y": 88, + "width": 160, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-invitations-label", + "type": "rectangle", + "x": 444, + "y": 104, + "width": 120, + "height": 16, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-container", + "type": "rectangle", + "x": 304, + "y": 152, + "width": 1112, + "height": 716, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "toolbar", + "type": "rectangle", + "x": 304, + "y": 152, + "width": 1112, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-input", + "type": "rectangle", + "x": 316, + "y": 162, + "width": 240, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role", + "type": "rectangle", + "x": 568, + "y": 162, + "width": 120, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role-arrow", + "type": "rectangle", + "x": 668, + "y": 176, + "width": 8, + "height": 8, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-header-row", + "type": "rectangle", + "x": 304, + "y": 208, + "width": 1112, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-member-col", + "type": "rectangle", + "x": 320, + "y": 226, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "header-row-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-role-col", + "type": "rectangle", + "x": 600, + "y": 226, + "width": 40, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "header-row-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-joined-col", + "type": "rectangle", + "x": 880, + "y": 226, + "width": 50, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "header-row-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-actions-col", + "type": "rectangle", + "x": 1340, + "y": 226, + "width": 50, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "header-row-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-1", + "type": "rectangle", + "x": 304, + "y": 256, + "width": 1112, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-avatar", + "type": "rectangle", + "x": 320, + "y": 274, + "width": 28, + "height": 28, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 14 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-name", + "type": "rectangle", + "x": 360, + "y": 270, + "width": 100, + "height": 14, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-email", + "type": "rectangle", + "x": 360, + "y": 288, + "width": 140, + "height": 10, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-dropdown", + "type": "rectangle", + "x": 600, + "y": 276, + "width": 100, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-arrow", + "type": "rectangle", + "x": 680, + "y": 284, + "width": 8, + "height": 8, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-joined", + "type": "rectangle", + "x": 880, + "y": 282, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-remove", + "type": "rectangle", + "x": 1356, + "y": 278, + "width": 24, + "height": 20, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-2", + "type": "rectangle", + "x": 304, + "y": 320, + "width": 1112, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-avatar", + "type": "rectangle", + "x": 320, + "y": 338, + "width": 28, + "height": 28, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 14 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-name", + "type": "rectangle", + "x": 360, + "y": 334, + "width": 80, + "height": 14, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-email", + "type": "rectangle", + "x": 360, + "y": 352, + "width": 120, + "height": 10, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-dropdown", + "type": "rectangle", + "x": 600, + "y": 340, + "width": 100, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-arrow", + "type": "rectangle", + "x": 680, + "y": 348, + "width": 8, + "height": 8, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-joined", + "type": "rectangle", + "x": 880, + "y": 346, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-remove", + "type": "rectangle", + "x": 1356, + "y": 342, + "width": 24, + "height": 20, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-3", + "type": "rectangle", + "x": 304, + "y": 384, + "width": 1112, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-avatar", + "type": "rectangle", + "x": 320, + "y": 402, + "width": 28, + "height": 28, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 14 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-name", + "type": "rectangle", + "x": 360, + "y": 398, + "width": 110, + "height": 14, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-email", + "type": "rectangle", + "x": 360, + "y": 416, + "width": 150, + "height": 10, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-dropdown", + "type": "rectangle", + "x": 600, + "y": 404, + "width": 100, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-arrow", + "type": "rectangle", + "x": 680, + "y": 412, + "width": 8, + "height": 8, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-joined", + "type": "rectangle", + "x": 880, + "y": 410, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-remove", + "type": "rectangle", + "x": 1356, + "y": 406, + "width": 24, + "height": 20, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-4", + "type": "rectangle", + "x": 304, + "y": 448, + "width": 1112, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-4-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-avatar", + "type": "rectangle", + "x": 320, + "y": 466, + "width": 28, + "height": 28, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-4-group"], + "roundness": { "type": 3, "value": 14 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-name", + "type": "rectangle", + "x": 360, + "y": 462, + "width": 90, + "height": 14, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-4-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-email", + "type": "rectangle", + "x": 360, + "y": 480, + "width": 130, + "height": 10, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-4-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-role-dropdown", + "type": "rectangle", + "x": 600, + "y": 468, + "width": 100, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-4-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-role-arrow", + "type": "rectangle", + "x": 680, + "y": 476, + "width": 8, + "height": 8, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-4-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-joined", + "type": "rectangle", + "x": 880, + "y": 474, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-4-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-remove", + "type": "rectangle", + "x": 1356, + "y": 470, + "width": 24, + "height": 20, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-4-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-5", + "type": "rectangle", + "x": 304, + "y": 512, + "width": 1112, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-5-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-avatar", + "type": "rectangle", + "x": 320, + "y": 530, + "width": 28, + "height": 28, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-5-group"], + "roundness": { "type": 3, "value": 14 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-name", + "type": "rectangle", + "x": 360, + "y": 526, + "width": 95, + "height": 14, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-5-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-email", + "type": "rectangle", + "x": 360, + "y": 544, + "width": 135, + "height": 10, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-5-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-role-dropdown", + "type": "rectangle", + "x": 600, + "y": 532, + "width": 100, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-5-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-role-arrow", + "type": "rectangle", + "x": 680, + "y": 540, + "width": 8, + "height": 8, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-5-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-joined", + "type": "rectangle", + "x": 880, + "y": 538, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-5-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-remove", + "type": "rectangle", + "x": 1356, + "y": 534, + "width": 24, + "height": 20, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-5-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-6", + "type": "rectangle", + "x": 304, + "y": 576, + "width": 1112, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-6-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-6-avatar", + "type": "rectangle", + "x": 320, + "y": 594, + "width": 28, + "height": 28, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-6-group"], + "roundness": { "type": 3, "value": 14 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-6-name", + "type": "rectangle", + "x": 360, + "y": 590, + "width": 85, + "height": 14, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-6-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-6-email", + "type": "rectangle", + "x": 360, + "y": 608, + "width": 125, + "height": 10, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-6-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-6-role-dropdown", + "type": "rectangle", + "x": 600, + "y": 596, + "width": 100, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-6-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-6-role-arrow", + "type": "rectangle", + "x": 680, + "y": 604, + "width": 8, + "height": 8, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-6-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-6-joined", + "type": "rectangle", + "x": 880, + "y": 602, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-6-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-6-remove", + "type": "rectangle", + "x": 1356, + "y": 598, + "width": 24, + "height": 20, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-6-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "empty-rows-area", + "type": "rectangle", + "x": 304, + "y": 640, + "width": 1112, + "height": 180, + "strokeColor": "transparent", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-bar", + "type": "rectangle", + "x": 304, + "y": 820, + "width": 1112, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-info", + "type": "rectangle", + "x": 328, + "y": 836, + "width": 100, + "height": 16, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-prev", + "type": "rectangle", + "x": 1244, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-1", + "type": "rectangle", + "x": 1284, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-2", + "type": "rectangle", + "x": 1324, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-next", + "type": "rectangle", + "x": 1364, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/low-fidelity/data-table-users.excalidraw b/.context/turbostarter-framework-context/wireframes/low-fidelity/data-table-users.excalidraw new file mode 100644 index 0000000..ffd1a6f --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/low-fidelity/data-table-users.excalidraw @@ -0,0 +1,2351 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-section-label", + "type": "rectangle", + "x": 20, + "y": 80, + "width": 60, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-active", + "type": "rectangle", + "x": 20, + "y": 148, + "width": 240, + "height": 40, + "strokeColor": "$primary", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-icon", + "type": "rectangle", + "x": 32, + "y": 158, + "width": 20, + "height": 20, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-label", + "type": "rectangle", + "x": 60, + "y": 162, + "width": 50, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings", + "type": "rectangle", + "x": 20, + "y": 196, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-analytics", + "type": "rectangle", + "x": 20, + "y": 244, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "rectangle", + "x": 304, + "y": 20, + "width": 80, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-count-badge", + "type": "rectangle", + "x": 392, + "y": 20, + "width": 48, + "height": 24, + "strokeColor": "transparent", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-container", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 1112, + "height": 780, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "toolbar", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 1112, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-input", + "type": "rectangle", + "x": 316, + "y": 98, + "width": 200, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role", + "type": "rectangle", + "x": 528, + "y": 98, + "width": 100, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role-arrow", + "type": "rectangle", + "x": 608, + "y": 112, + "width": 8, + "height": 8, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-2fa", + "type": "rectangle", + "x": 640, + "y": 98, + "width": 100, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-2fa-arrow", + "type": "rectangle", + "x": 720, + "y": 112, + "width": 8, + "height": 8, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-status", + "type": "rectangle", + "x": 752, + "y": 98, + "width": 100, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-status-arrow", + "type": "rectangle", + "x": 832, + "y": 112, + "width": 8, + "height": 8, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-date", + "type": "rectangle", + "x": 864, + "y": 98, + "width": 120, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-date-arrow", + "type": "rectangle", + "x": 964, + "y": 112, + "width": 8, + "height": 8, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "view-options", + "type": "rectangle", + "x": 1316, + "y": 98, + "width": 88, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-header-row", + "type": "rectangle", + "x": 304, + "y": 144, + "width": 1112, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-checkbox", + "type": "rectangle", + "x": 320, + "y": 158, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "header-row-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-user-col", + "type": "rectangle", + "x": 360, + "y": 162, + "width": 100, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "header-row-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-role-col", + "type": "rectangle", + "x": 580, + "y": 162, + "width": 40, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "header-row-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-2fa-col", + "type": "rectangle", + "x": 700, + "y": 162, + "width": 30, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "header-row-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-status-col", + "type": "rectangle", + "x": 820, + "y": 162, + "width": 50, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "header-row-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-created-col", + "type": "rectangle", + "x": 980, + "y": 162, + "width": 60, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "header-row-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-actions-col", + "type": "rectangle", + "x": 1340, + "y": 162, + "width": 50, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "header-row-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-1", + "type": "rectangle", + "x": 304, + "y": 192, + "width": 1112, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-checkbox", + "type": "rectangle", + "x": 320, + "y": 210, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-avatar", + "type": "rectangle", + "x": 360, + "y": 206, + "width": 28, + "height": 28, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 14 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-name", + "type": "rectangle", + "x": 396, + "y": 206, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-email", + "type": "rectangle", + "x": 396, + "y": 222, + "width": 120, + "height": 10, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-badge", + "type": "rectangle", + "x": 580, + "y": 210, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-2fa-icon", + "type": "rectangle", + "x": 700, + "y": 210, + "width": 20, + "height": 20, + "strokeColor": "$success", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status-badge", + "type": "rectangle", + "x": 820, + "y": 210, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-created", + "type": "rectangle", + "x": 980, + "y": 214, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-actions", + "type": "rectangle", + "x": 1356, + "y": 210, + "width": 24, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-2", + "type": "rectangle", + "x": 304, + "y": 248, + "width": 1112, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-checkbox", + "type": "rectangle", + "x": 320, + "y": 266, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-avatar", + "type": "rectangle", + "x": 360, + "y": 262, + "width": 28, + "height": 28, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 14 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-name", + "type": "rectangle", + "x": 396, + "y": 262, + "width": 100, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-email", + "type": "rectangle", + "x": 396, + "y": 278, + "width": 140, + "height": 10, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-badge", + "type": "rectangle", + "x": 580, + "y": 266, + "width": 50, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-2fa-icon", + "type": "rectangle", + "x": 700, + "y": 266, + "width": 20, + "height": 20, + "strokeColor": "$success", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status-badge", + "type": "rectangle", + "x": 820, + "y": 266, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-created", + "type": "rectangle", + "x": 980, + "y": 270, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-actions", + "type": "rectangle", + "x": 1356, + "y": 266, + "width": 24, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-3", + "type": "rectangle", + "x": 304, + "y": 304, + "width": 1112, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-checkbox", + "type": "rectangle", + "x": 320, + "y": 322, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-avatar", + "type": "rectangle", + "x": 360, + "y": 318, + "width": 28, + "height": 28, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 14 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-name", + "type": "rectangle", + "x": 396, + "y": 318, + "width": 90, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-email", + "type": "rectangle", + "x": 396, + "y": 334, + "width": 110, + "height": 10, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-badge", + "type": "rectangle", + "x": 580, + "y": 322, + "width": 50, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-2fa-icon", + "type": "rectangle", + "x": 700, + "y": 322, + "width": 20, + "height": 20, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-status-badge", + "type": "rectangle", + "x": 820, + "y": 322, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-created", + "type": "rectangle", + "x": 980, + "y": 326, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-actions", + "type": "rectangle", + "x": 1356, + "y": 322, + "width": 24, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-4", + "type": "rectangle", + "x": 304, + "y": 360, + "width": 1112, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-4-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-checkbox", + "type": "rectangle", + "x": 320, + "y": 378, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-4-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-avatar", + "type": "rectangle", + "x": 360, + "y": 374, + "width": 28, + "height": 28, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-4-group"], + "roundness": { "type": 3, "value": 14 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-name", + "type": "rectangle", + "x": 396, + "y": 374, + "width": 70, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-4-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-email", + "type": "rectangle", + "x": 396, + "y": 390, + "width": 130, + "height": 10, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-4-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-role-badge", + "type": "rectangle", + "x": 580, + "y": 378, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-4-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-2fa-icon", + "type": "rectangle", + "x": 700, + "y": 378, + "width": 20, + "height": 20, + "strokeColor": "$success", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-4-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-status-badge", + "type": "rectangle", + "x": 820, + "y": 378, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-4-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-created", + "type": "rectangle", + "x": 980, + "y": 382, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-4-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-actions", + "type": "rectangle", + "x": 1356, + "y": 378, + "width": 24, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-4-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-5", + "type": "rectangle", + "x": 304, + "y": 416, + "width": 1112, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-5-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-checkbox", + "type": "rectangle", + "x": 320, + "y": 434, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-5-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-avatar", + "type": "rectangle", + "x": 360, + "y": 430, + "width": 28, + "height": 28, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-5-group"], + "roundness": { "type": 3, "value": 14 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-name", + "type": "rectangle", + "x": 396, + "y": 430, + "width": 85, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-5-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-email", + "type": "rectangle", + "x": 396, + "y": 446, + "width": 125, + "height": 10, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-5-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-role-badge", + "type": "rectangle", + "x": 580, + "y": 434, + "width": 50, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-5-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-2fa-icon", + "type": "rectangle", + "x": 700, + "y": 434, + "width": 20, + "height": 20, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-5-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-status-badge", + "type": "rectangle", + "x": 820, + "y": 434, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-5-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-created", + "type": "rectangle", + "x": 980, + "y": 438, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-5-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-actions", + "type": "rectangle", + "x": 1356, + "y": 434, + "width": 24, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-5-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-6", + "type": "rectangle", + "x": 304, + "y": 472, + "width": 1112, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-6-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-6-checkbox", + "type": "rectangle", + "x": 320, + "y": 490, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-6-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-6-avatar", + "type": "rectangle", + "x": 360, + "y": 486, + "width": 28, + "height": 28, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-6-group"], + "roundness": { "type": 3, "value": 14 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-6-name", + "type": "rectangle", + "x": 396, + "y": 486, + "width": 95, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-6-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-6-email", + "type": "rectangle", + "x": 396, + "y": 502, + "width": 135, + "height": 10, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-6-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-6-role-badge", + "type": "rectangle", + "x": 580, + "y": 490, + "width": 50, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-6-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-6-2fa-icon", + "type": "rectangle", + "x": 700, + "y": 490, + "width": 20, + "height": 20, + "strokeColor": "$success", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-6-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-6-status-badge", + "type": "rectangle", + "x": 820, + "y": 490, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-6-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-6-created", + "type": "rectangle", + "x": 980, + "y": 494, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-6-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-6-actions", + "type": "rectangle", + "x": 1356, + "y": 490, + "width": 24, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-6-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-7", + "type": "rectangle", + "x": 304, + "y": 528, + "width": 1112, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-7-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-7-checkbox", + "type": "rectangle", + "x": 320, + "y": 546, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-7-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-7-avatar", + "type": "rectangle", + "x": 360, + "y": 542, + "width": 28, + "height": 28, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-7-group"], + "roundness": { "type": 3, "value": 14 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-7-name", + "type": "rectangle", + "x": 396, + "y": 542, + "width": 75, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-7-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-7-email", + "type": "rectangle", + "x": 396, + "y": 558, + "width": 115, + "height": 10, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-7-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-7-role-badge", + "type": "rectangle", + "x": 580, + "y": 546, + "width": 50, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-7-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-7-2fa-icon", + "type": "rectangle", + "x": 700, + "y": 546, + "width": 20, + "height": 20, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-7-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-7-status-badge", + "type": "rectangle", + "x": 820, + "y": 546, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-7-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-7-created", + "type": "rectangle", + "x": 980, + "y": 550, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-7-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-7-actions", + "type": "rectangle", + "x": 1356, + "y": 546, + "width": 24, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-7-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "empty-rows-area", + "type": "rectangle", + "x": 304, + "y": 584, + "width": 1112, + "height": 236, + "strokeColor": "transparent", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-bar", + "type": "rectangle", + "x": 304, + "y": 820, + "width": 1112, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-info", + "type": "rectangle", + "x": 328, + "y": 836, + "width": 120, + "height": 16, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-prev", + "type": "rectangle", + "x": 1124, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-1", + "type": "rectangle", + "x": 1164, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-2", + "type": "rectangle", + "x": 1204, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-3", + "type": "rectangle", + "x": 1244, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-ellipsis", + "type": "rectangle", + "x": 1284, + "y": 840, + "width": 20, + "height": 8, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-10", + "type": "rectangle", + "x": 1316, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-next", + "type": "rectangle", + "x": 1356, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/low-fidelity/settings-billing.excalidraw b/.context/turbostarter-framework-context/wireframes/low-fidelity/settings-billing.excalidraw new file mode 100644 index 0000000..b5751fd --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/low-fidelity/settings-billing.excalidraw @@ -0,0 +1,1251 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-1", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-2", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-3", + "type": "rectangle", + "x": 20, + "y": 220, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-active", + "type": "rectangle", + "x": 20, + "y": 280, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "rectangle", + "x": 304, + "y": 20, + "width": 120, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-general", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-security", + "type": "rectangle", + "x": 404, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-billing-active", + "type": "rectangle", + "x": 504, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-card", + "type": "rectangle", + "x": 304, + "y": 140, + "width": 540, + "height": 200, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["plan-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-title", + "type": "rectangle", + "x": 324, + "y": 160, + "width": 120, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["plan-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-name-badge", + "type": "rectangle", + "x": 324, + "y": 200, + "width": 60, + "height": 28, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["plan-section"], + "roundness": { "type": 3, "value": 14 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-price", + "type": "rectangle", + "x": 400, + "y": 200, + "width": 100, + "height": 28, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["plan-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-feature-1", + "type": "rectangle", + "x": 324, + "y": 244, + "width": 200, + "height": 14, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["plan-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-feature-2", + "type": "rectangle", + "x": 324, + "y": 264, + "width": 180, + "height": 14, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["plan-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-feature-3", + "type": "rectangle", + "x": 324, + "y": 284, + "width": 160, + "height": 14, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["plan-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "manage-subscription-button", + "type": "rectangle", + "x": 680, + "y": 180, + "width": 140, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["plan-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-card", + "type": "rectangle", + "x": 864, + "y": 140, + "width": 280, + "height": 200, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["credits-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-title", + "type": "rectangle", + "x": 884, + "y": 160, + "width": 80, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["credits-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-balance", + "type": "rectangle", + "x": 884, + "y": 200, + "width": 120, + "height": 32, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["credits-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-usage-bar-bg", + "type": "rectangle", + "x": 884, + "y": 252, + "width": 240, + "height": 12, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 40, + "groupIds": ["credits-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-usage-bar-fill", + "type": "rectangle", + "x": 884, + "y": 252, + "width": 168, + "height": 12, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["credits-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "buy-credits-button", + "type": "rectangle", + "x": 884, + "y": 284, + "width": 120, + "height": 40, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["credits-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "payment-method-card", + "type": "rectangle", + "x": 304, + "y": 360, + "width": 540, + "height": 100, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["payment-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "payment-title", + "type": "rectangle", + "x": 324, + "y": 380, + "width": 140, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["payment-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-icon", + "type": "rectangle", + "x": 324, + "y": 416, + "width": 40, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["payment-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-last-digits", + "type": "rectangle", + "x": 380, + "y": 420, + "width": 100, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["payment-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "update-payment-button", + "type": "rectangle", + "x": 720, + "y": 400, + "width": 100, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["payment-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "billing-history-card", + "type": "rectangle", + "x": 304, + "y": 480, + "width": 840, + "height": 340, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "history-title", + "type": "rectangle", + "x": 324, + "y": 500, + "width": 140, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-header", + "type": "rectangle", + "x": 324, + "y": 540, + "width": 800, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 40, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-date", + "type": "rectangle", + "x": 344, + "y": 552, + "width": 60, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-amount", + "type": "rectangle", + "x": 544, + "y": 552, + "width": 80, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-status", + "type": "rectangle", + "x": 744, + "y": 552, + "width": 60, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invoice", + "type": "rectangle", + "x": 944, + "y": 552, + "width": 80, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1", + "type": "rectangle", + "x": 324, + "y": 580, + "width": 800, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-date", + "type": "rectangle", + "x": 344, + "y": 596, + "width": 100, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-amount", + "type": "rectangle", + "x": 544, + "y": 596, + "width": 60, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status", + "type": "rectangle", + "x": 744, + "y": 592, + "width": 60, + "height": 24, + "strokeColor": "$success", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-invoice", + "type": "rectangle", + "x": 944, + "y": 596, + "width": 80, + "height": 16, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2", + "type": "rectangle", + "x": 324, + "y": 628, + "width": 800, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-date", + "type": "rectangle", + "x": 344, + "y": 644, + "width": 100, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-amount", + "type": "rectangle", + "x": 544, + "y": 644, + "width": 60, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status", + "type": "rectangle", + "x": 744, + "y": 640, + "width": 60, + "height": 24, + "strokeColor": "$success", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-invoice", + "type": "rectangle", + "x": 944, + "y": 644, + "width": 80, + "height": 16, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3", + "type": "rectangle", + "x": 324, + "y": 676, + "width": 800, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-date", + "type": "rectangle", + "x": 344, + "y": 692, + "width": 100, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-amount", + "type": "rectangle", + "x": 544, + "y": 692, + "width": 60, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-status", + "type": "rectangle", + "x": 744, + "y": 688, + "width": 60, + "height": 24, + "strokeColor": "$success", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-invoice", + "type": "rectangle", + "x": 944, + "y": 692, + "width": 80, + "height": 16, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4", + "type": "rectangle", + "x": 324, + "y": 724, + "width": 800, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-date", + "type": "rectangle", + "x": 344, + "y": 740, + "width": 100, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-amount", + "type": "rectangle", + "x": 544, + "y": 740, + "width": 60, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-status", + "type": "rectangle", + "x": 744, + "y": 736, + "width": 60, + "height": 24, + "strokeColor": "$success", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-invoice", + "type": "rectangle", + "x": 944, + "y": 740, + "width": 80, + "height": 16, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/low-fidelity/settings-general.excalidraw b/.context/turbostarter-framework-context/wireframes/low-fidelity/settings-general.excalidraw new file mode 100644 index 0000000..07e1b66 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/low-fidelity/settings-general.excalidraw @@ -0,0 +1,711 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-1", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-2", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-3", + "type": "rectangle", + "x": 20, + "y": 220, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-active", + "type": "rectangle", + "x": 20, + "y": 280, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "rectangle", + "x": 304, + "y": 20, + "width": 120, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-general-active", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-security", + "type": "rectangle", + "x": 404, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-billing", + "type": "rectangle", + "x": 504, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "profile-card", + "type": "rectangle", + "x": 304, + "y": 140, + "width": 700, + "height": 280, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "profile-card-title", + "type": "rectangle", + "x": 324, + "y": 160, + "width": 100, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "avatar-circle", + "type": "ellipse", + "x": 324, + "y": 200, + "width": 80, + "height": 80, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "avatar-change-button", + "type": "rectangle", + "x": 324, + "y": 300, + "width": 80, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-label", + "type": "rectangle", + "x": 440, + "y": 200, + "width": 60, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["profile-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-input", + "type": "rectangle", + "x": 440, + "y": 220, + "width": 540, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-label", + "type": "rectangle", + "x": 440, + "y": 280, + "width": 60, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["profile-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-input", + "type": "rectangle", + "x": 440, + "y": 300, + "width": 440, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "verified-badge", + "type": "rectangle", + "x": 900, + "y": 308, + "width": 80, + "height": 24, + "strokeColor": "$success", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "save-profile-button", + "type": "rectangle", + "x": 324, + "y": 360, + "width": 100, + "height": 40, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-card", + "type": "rectangle", + "x": 304, + "y": 440, + "width": 700, + "height": 140, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["language-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-card-title", + "type": "rectangle", + "x": 324, + "y": 460, + "width": 100, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["language-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-label", + "type": "rectangle", + "x": 324, + "y": 500, + "width": 80, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["language-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-dropdown", + "type": "rectangle", + "x": 324, + "y": 520, + "width": 300, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["language-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "dropdown-chevron", + "type": "rectangle", + "x": 588, + "y": 532, + "width": 16, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["language-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "danger-zone-card", + "type": "rectangle", + "x": 304, + "y": 600, + "width": 700, + "height": 140, + "strokeColor": "$destructive", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["danger-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "danger-zone-title", + "type": "rectangle", + "x": 324, + "y": 620, + "width": 120, + "height": 20, + "strokeColor": "$destructive", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["danger-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "danger-zone-description", + "type": "rectangle", + "x": 324, + "y": 660, + "width": 400, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["danger-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "delete-account-button", + "type": "rectangle", + "x": 324, + "y": 700, + "width": 140, + "height": 40, + "strokeColor": "$destructive", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["danger-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/low-fidelity/settings-security.excalidraw b/.context/turbostarter-framework-context/wireframes/low-fidelity/settings-security.excalidraw new file mode 100644 index 0000000..f947d0b --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/low-fidelity/settings-security.excalidraw @@ -0,0 +1,1091 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-1", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-2", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-3", + "type": "rectangle", + "x": 20, + "y": 220, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-active", + "type": "rectangle", + "x": 20, + "y": 280, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "rectangle", + "x": 304, + "y": 20, + "width": 120, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-general", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-security-active", + "type": "rectangle", + "x": 404, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-billing", + "type": "rectangle", + "x": 504, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-card", + "type": "rectangle", + "x": 304, + "y": 140, + "width": 700, + "height": 140, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["twofa-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-title", + "type": "rectangle", + "x": 324, + "y": 160, + "width": 200, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["twofa-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-status-indicator", + "type": "rectangle", + "x": 324, + "y": 200, + "width": 80, + "height": 24, + "strokeColor": "$success", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["twofa-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-description", + "type": "rectangle", + "x": 324, + "y": 240, + "width": 500, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["twofa-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-toggle-button", + "type": "rectangle", + "x": 880, + "y": 180, + "width": 100, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["twofa-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkeys-card", + "type": "rectangle", + "x": 304, + "y": 300, + "width": 700, + "height": 220, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["passkeys-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkeys-title", + "type": "rectangle", + "x": 324, + "y": 320, + "width": 100, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["passkeys-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-item-1", + "type": "rectangle", + "x": 324, + "y": 360, + "width": 656, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["passkeys-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-1-name", + "type": "rectangle", + "x": 340, + "y": 372, + "width": 120, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["passkeys-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-1-date", + "type": "rectangle", + "x": 500, + "y": 372, + "width": 100, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["passkeys-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-1-delete", + "type": "rectangle", + "x": 920, + "y": 368, + "width": 48, + "height": 24, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["passkeys-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-item-2", + "type": "rectangle", + "x": 324, + "y": 420, + "width": 656, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["passkeys-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-2-name", + "type": "rectangle", + "x": 340, + "y": 432, + "width": 140, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["passkeys-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-2-date", + "type": "rectangle", + "x": 520, + "y": 432, + "width": 100, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["passkeys-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-2-delete", + "type": "rectangle", + "x": 920, + "y": 428, + "width": 48, + "height": 24, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["passkeys-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "add-passkey-button", + "type": "rectangle", + "x": 324, + "y": 480, + "width": 120, + "height": 40, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["passkeys-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sessions-card", + "type": "rectangle", + "x": 304, + "y": 540, + "width": 700, + "height": 300, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sessions-title", + "type": "rectangle", + "x": 324, + "y": 560, + "width": 140, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-item-1", + "type": "rectangle", + "x": 324, + "y": 600, + "width": 656, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-icon", + "type": "rectangle", + "x": 340, + "y": 612, + "width": 24, + "height": 24, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-name", + "type": "rectangle", + "x": 380, + "y": 608, + "width": 160, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-location", + "type": "rectangle", + "x": 380, + "y": 628, + "width": 120, + "height": 12, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-current-badge", + "type": "rectangle", + "x": 900, + "y": 612, + "width": 68, + "height": 24, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-item-2", + "type": "rectangle", + "x": 324, + "y": 660, + "width": 656, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-icon", + "type": "rectangle", + "x": 340, + "y": 672, + "width": 24, + "height": 24, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-name", + "type": "rectangle", + "x": 380, + "y": 668, + "width": 140, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-location", + "type": "rectangle", + "x": 380, + "y": 688, + "width": 100, + "height": 12, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-revoke", + "type": "rectangle", + "x": 908, + "y": 672, + "width": 60, + "height": 24, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-item-3", + "type": "rectangle", + "x": 324, + "y": 720, + "width": 656, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-3-icon", + "type": "rectangle", + "x": 340, + "y": 732, + "width": 24, + "height": 24, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-3-name", + "type": "rectangle", + "x": 380, + "y": 728, + "width": 180, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-3-location", + "type": "rectangle", + "x": 380, + "y": 748, + "width": 140, + "height": 12, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-3-revoke", + "type": "rectangle", + "x": 908, + "y": 732, + "width": 60, + "height": 24, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-item-4", + "type": "rectangle", + "x": 324, + "y": 780, + "width": 656, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-4-icon", + "type": "rectangle", + "x": 340, + "y": 792, + "width": 24, + "height": 24, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-4-name", + "type": "rectangle", + "x": 380, + "y": 788, + "width": 120, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-4-location", + "type": "rectangle", + "x": 380, + "y": 808, + "width": 80, + "height": 12, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-4-revoke", + "type": "rectangle", + "x": 908, + "y": 792, + "width": 60, + "height": 24, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/low-fidelity/sidebar-admin.excalidraw b/.context/turbostarter-framework-context/wireframes/low-fidelity/sidebar-admin.excalidraw new file mode 100644 index 0000000..aadf04a --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/low-fidelity/sidebar-admin.excalidraw @@ -0,0 +1,648 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "sidebar-container", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-area", + "type": "rectangle", + "x": 24, + "y": 24, + "width": 160, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 44, + "y": 34, + "width": 80, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Logo", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 14 + }, + { + "id": "admin-badge", + "type": "rectangle", + "x": 196, + "y": 32, + "width": 60, + "height": 24, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "admin-badge-text", + "type": "text", + "x": 206, + "y": 36, + "width": 40, + "height": 16, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Admin", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 10 + }, + { + "id": "section-divider-1", + "type": "line", + "x": 24, + "y": 88, + "width": 232, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [232, 0]] + }, + { + "id": "section-label-admin", + "type": "rectangle", + "x": 24, + "y": 100, + "width": 50, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["sidebar-group", "section-admin"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-home-active", + "type": "rectangle", + "x": 24, + "y": 120, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-home-icon", + "type": "rectangle", + "x": 36, + "y": 130, + "width": 20, + "height": 20, + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-home-text", + "type": "text", + "x": 64, + "y": 130, + "width": 60, + "height": 20, + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Home", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-users", + "type": "rectangle", + "x": 24, + "y": 168, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-users-icon", + "type": "rectangle", + "x": 36, + "y": 178, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-users-text", + "type": "text", + "x": 64, + "y": 178, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Users", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-orgs", + "type": "rectangle", + "x": 24, + "y": 216, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-orgs-icon", + "type": "rectangle", + "x": 36, + "y": 226, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-orgs-text", + "type": "text", + "x": 64, + "y": 226, + "width": 100, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Organizations", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-customers", + "type": "rectangle", + "x": 24, + "y": 264, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-customers-icon", + "type": "rectangle", + "x": 36, + "y": 274, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-customers-text", + "type": "text", + "x": 64, + "y": 274, + "width": 80, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Customers", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-2", + "type": "line", + "x": 24, + "y": 324, + "width": 232, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [232, 0]] + }, + { + "id": "nav-back", + "type": "rectangle", + "x": 24, + "y": 344, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-back"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-back-icon", + "type": "rectangle", + "x": 36, + "y": 354, + "width": 20, + "height": 20, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-back"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-back-text", + "type": "text", + "x": 64, + "y": 354, + "width": 140, + "height": 20, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-back"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Back to Dashboard", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-footer", + "type": "line", + "x": 24, + "y": 812, + "width": 232, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [232, 0]] + }, + { + "id": "user-footer", + "type": "rectangle", + "x": 24, + "y": 824, + "width": 232, + "height": 64, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-avatar", + "type": "ellipse", + "x": 24, + "y": 836, + "width": 40, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-name", + "type": "text", + "x": 76, + "y": 838, + "width": 100, + "height": 16, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Admin User", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 12 + }, + { + "id": "role-badge", + "type": "rectangle", + "x": 76, + "y": 858, + "width": 60, + "height": 18, + "strokeColor": "$destructive", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["sidebar-group", "user-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "role-badge-text", + "type": "text", + "x": 86, + "y": 860, + "width": 40, + "height": 14, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Admin", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 9 + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/low-fidelity/sidebar-apps.excalidraw b/.context/turbostarter-framework-context/wireframes/low-fidelity/sidebar-apps.excalidraw new file mode 100644 index 0000000..2f4be45 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/low-fidelity/sidebar-apps.excalidraw @@ -0,0 +1,1079 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "sidebar-container", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-area", + "type": "rectangle", + "x": 24, + "y": 24, + "width": 232, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 44, + "y": 34, + "width": 80, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Logo", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 14 + }, + { + "id": "section-divider-1", + "type": "line", + "x": 24, + "y": 88, + "width": 232, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [232, 0]] + }, + { + "id": "section-label-apps", + "type": "rectangle", + "x": 24, + "y": 100, + "width": 40, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["sidebar-group", "section-apps"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-chat-active", + "type": "rectangle", + "x": 24, + "y": 120, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-chat-icon", + "type": "rectangle", + "x": 36, + "y": 130, + "width": 20, + "height": 20, + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-chat-text", + "type": "text", + "x": 64, + "y": 130, + "width": 60, + "height": 20, + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Chat", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-image", + "type": "rectangle", + "x": 24, + "y": 168, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-image-icon", + "type": "rectangle", + "x": 36, + "y": 178, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-image-text", + "type": "text", + "x": 64, + "y": 178, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Image", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-tts", + "type": "rectangle", + "x": 24, + "y": 216, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-tts-icon", + "type": "rectangle", + "x": 36, + "y": 226, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-tts-text", + "type": "text", + "x": 64, + "y": 226, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "TTS", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-pdf", + "type": "rectangle", + "x": 24, + "y": 264, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-pdf-icon", + "type": "rectangle", + "x": 36, + "y": 274, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-pdf-text", + "type": "text", + "x": 64, + "y": 274, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "PDF", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-agent", + "type": "rectangle", + "x": 24, + "y": 312, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-agent-icon", + "type": "rectangle", + "x": 36, + "y": 322, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-agent-text", + "type": "text", + "x": 64, + "y": 322, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Agent", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-2", + "type": "line", + "x": 24, + "y": 372, + "width": 232, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [232, 0]] + }, + { + "id": "section-label-free-tools", + "type": "rectangle", + "x": 24, + "y": 384, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["sidebar-group", "section-free-tools"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-envin", + "type": "rectangle", + "x": 24, + "y": 404, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-envin-icon", + "type": "rectangle", + "x": 36, + "y": 414, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-envin-text", + "type": "text", + "x": 64, + "y": 414, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Envin", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-ideas", + "type": "rectangle", + "x": 24, + "y": 452, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-ideas-icon", + "type": "rectangle", + "x": 36, + "y": 462, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-ideas-text", + "type": "text", + "x": 64, + "y": 462, + "width": 100, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Ideas Generator", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-extro", + "type": "rectangle", + "x": 24, + "y": 500, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-extro-icon", + "type": "rectangle", + "x": 36, + "y": 510, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-extro-text", + "type": "text", + "x": 64, + "y": 510, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Extro", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-emojai", + "type": "rectangle", + "x": 24, + "y": 548, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-emojai-icon", + "type": "rectangle", + "x": 36, + "y": 558, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-emojai-text", + "type": "text", + "x": 64, + "y": 558, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Emojai", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-3", + "type": "line", + "x": 24, + "y": 608, + "width": 232, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [232, 0]] + }, + { + "id": "section-label-other", + "type": "rectangle", + "x": 24, + "y": 620, + "width": 50, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["sidebar-group", "section-other"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-home", + "type": "rectangle", + "x": 24, + "y": 640, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-other"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-home-icon", + "type": "rectangle", + "x": 36, + "y": 650, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-other"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-home-text", + "type": "text", + "x": 64, + "y": 650, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-other"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Home", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-docs", + "type": "rectangle", + "x": 24, + "y": 688, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-other"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-docs-icon", + "type": "rectangle", + "x": 36, + "y": 698, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-other"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-docs-text", + "type": "text", + "x": 64, + "y": 698, + "width": 100, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-other"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Documentation", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-blog", + "type": "rectangle", + "x": 24, + "y": 736, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-other"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-blog-icon", + "type": "rectangle", + "x": 36, + "y": 746, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-other"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-blog-text", + "type": "text", + "x": 64, + "y": 746, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-other"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Blog", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-footer", + "type": "line", + "x": 24, + "y": 812, + "width": 232, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [232, 0]] + }, + { + "id": "user-footer", + "type": "rectangle", + "x": 24, + "y": 824, + "width": 232, + "height": 64, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-avatar", + "type": "ellipse", + "x": 24, + "y": 836, + "width": 40, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-name", + "type": "text", + "x": 76, + "y": 846, + "width": 120, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "User Name", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/low-fidelity/sidebar-dashboard.excalidraw b/.context/turbostarter-framework-context/wireframes/low-fidelity/sidebar-dashboard.excalidraw new file mode 100644 index 0000000..f087c6b --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/low-fidelity/sidebar-dashboard.excalidraw @@ -0,0 +1,724 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "sidebar-container", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-area", + "type": "rectangle", + "x": 24, + "y": 24, + "width": 232, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 44, + "y": 34, + "width": 80, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Logo", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 14 + }, + { + "id": "section-divider-1", + "type": "line", + "x": 24, + "y": 88, + "width": 232, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [232, 0]] + }, + { + "id": "section-label-platform", + "type": "rectangle", + "x": 24, + "y": 100, + "width": 70, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["sidebar-group", "section-platform"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-dashboard-active", + "type": "rectangle", + "x": 24, + "y": 120, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-platform"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-dashboard-icon", + "type": "rectangle", + "x": 36, + "y": 130, + "width": 20, + "height": 20, + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-platform"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-dashboard-text", + "type": "text", + "x": 64, + "y": 130, + "width": 80, + "height": 20, + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-platform"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Dashboard", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-ai-tools", + "type": "rectangle", + "x": 24, + "y": 168, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-platform"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-ai-tools-icon", + "type": "rectangle", + "x": 36, + "y": 178, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-platform"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-ai-tools-text", + "type": "text", + "x": 64, + "y": 178, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-platform"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "AI Tools", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-2", + "type": "line", + "x": 24, + "y": 228, + "width": 232, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [232, 0]] + }, + { + "id": "section-label-manage", + "type": "rectangle", + "x": 24, + "y": 240, + "width": 60, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["sidebar-group", "section-manage"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-settings", + "type": "rectangle", + "x": 24, + "y": 260, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-manage"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-settings-icon", + "type": "rectangle", + "x": 36, + "y": 270, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-manage"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-settings-text", + "type": "text", + "x": 64, + "y": 270, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-manage"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Settings", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-3", + "type": "line", + "x": 24, + "y": 320, + "width": 232, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [232, 0]] + }, + { + "id": "section-label-dev", + "type": "rectangle", + "x": 24, + "y": 332, + "width": 30, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["sidebar-group", "section-dev"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-demos", + "type": "rectangle", + "x": 24, + "y": 352, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-dev"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-demos-icon", + "type": "rectangle", + "x": 36, + "y": 362, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-dev"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-demos-text", + "type": "text", + "x": 64, + "y": 362, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-dev"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Demos", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-bottom", + "type": "line", + "x": 24, + "y": 700, + "width": 232, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [232, 0]] + }, + { + "id": "nav-support", + "type": "rectangle", + "x": 24, + "y": 712, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-bottom"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-support-icon", + "type": "rectangle", + "x": 36, + "y": 722, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-bottom"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-support-text", + "type": "text", + "x": 64, + "y": 722, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-bottom"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Support", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-feedback", + "type": "rectangle", + "x": 24, + "y": 760, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-bottom"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-feedback-icon", + "type": "rectangle", + "x": 36, + "y": 770, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-bottom"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-feedback-text", + "type": "text", + "x": 64, + "y": 770, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-bottom"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Feedback", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-footer", + "type": "line", + "x": 24, + "y": 812, + "width": 232, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [232, 0]] + }, + { + "id": "user-footer", + "type": "rectangle", + "x": 24, + "y": 824, + "width": 232, + "height": 64, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-avatar", + "type": "ellipse", + "x": 24, + "y": 836, + "width": 40, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-name", + "type": "text", + "x": 76, + "y": 846, + "width": 120, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "User Name", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "dropdown-indicator", + "type": "rectangle", + "x": 224, + "y": 848, + "width": 16, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/medium-fidelity/auth-forgot-password.excalidraw b/.context/turbostarter-framework-context/wireframes/medium-fidelity/auth-forgot-password.excalidraw new file mode 100644 index 0000000..d5f6c59 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/medium-fidelity/auth-forgot-password.excalidraw @@ -0,0 +1,436 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "left-column", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["left-column-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-column", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-column-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-placeholder", + "type": "rectangle", + "x": 300, + "y": 180, + "width": 120, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["logo-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 320, + "y": 190, + "width": 80, + "height": 20, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["logo-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "lock-icon", + "type": "ellipse", + "x": 336, + "y": 260, + "width": 48, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["icon-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "lock-icon-inner", + "type": "rectangle", + "x": 352, + "y": 276, + "width": 16, + "height": 16, + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["icon-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "forgot-password-title-text", + "type": "text", + "x": 200, + "y": 340, + "width": 320, + "height": 32, + "text": "Forgot password?", + "fontSize": 24, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["title-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "forgot-password-description-text", + "type": "text", + "x": 200, + "y": 380, + "width": 320, + "height": 40, + "text": "Enter your email to receive a reset link", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["title-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-label", + "type": "text", + "x": 200, + "y": 440, + "width": 100, + "height": 16, + "text": "Email address", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["email-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-input", + "type": "rectangle", + "x": 200, + "y": 460, + "width": 320, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["email-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-placeholder", + "type": "text", + "x": 212, + "y": 472, + "width": 200, + "height": 20, + "text": "you@example.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["email-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "send-reset-button", + "type": "rectangle", + "x": 200, + "y": 540, + "width": 320, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["button-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "send-reset-button-text", + "type": "text", + "x": 200, + "y": 552, + "width": 320, + "height": 20, + "text": "Send reset link", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$primary-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["button-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "back-arrow-icon", + "type": "rectangle", + "x": 290, + "y": 620, + "width": 16, + "height": 2, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["back-link-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "back-to-login-link-text", + "type": "text", + "x": 310, + "y": 612, + "width": 120, + "height": 20, + "text": "Back to sign in", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["back-link-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo", + "type": "rectangle", + "x": 1000, + "y": 400, + "width": 160, + "height": 60, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-branding-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo-text", + "type": "text", + "x": 1000, + "y": 420, + "width": 160, + "height": 20, + "text": "MCPGet", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$primary-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-branding-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-tagline", + "type": "text", + "x": 920, + "y": 480, + "width": 320, + "height": 24, + "text": "Discover and install MCPs with ease", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-column-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/medium-fidelity/auth-join-org.excalidraw b/.context/turbostarter-framework-context/wireframes/medium-fidelity/auth-join-org.excalidraw new file mode 100644 index 0000000..88fe448 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/medium-fidelity/auth-join-org.excalidraw @@ -0,0 +1,491 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "left-column", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["left-column-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-column", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-column-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-placeholder", + "type": "rectangle", + "x": 300, + "y": 100, + "width": 120, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["logo-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 320, + "y": 110, + "width": 80, + "height": 20, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["logo-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "join-org-title-text", + "type": "text", + "x": 200, + "y": 180, + "width": 320, + "height": 32, + "text": "Join Organization", + "fontSize": 24, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["title-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "join-org-subtitle-text", + "type": "text", + "x": 200, + "y": 216, + "width": 320, + "height": 20, + "text": "You've been invited to join", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["title-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "invitation-card", + "type": "rectangle", + "x": 180, + "y": 280, + "width": 360, + "height": 220, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["invitation-card-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "org-logo", + "type": "rectangle", + "x": 320, + "y": 300, + "width": 80, + "height": 80, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["invitation-card-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "org-logo-text", + "type": "text", + "x": 320, + "y": 330, + "width": 80, + "height": 20, + "text": "AC", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$primary-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["invitation-card-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "org-name-text", + "type": "text", + "x": 260, + "y": 400, + "width": 200, + "height": 24, + "text": "Acme Corp", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["invitation-card-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "inviter-name-text", + "type": "text", + "x": 280, + "y": 434, + "width": 160, + "height": 16, + "text": "Invited by John Doe", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["invitation-card-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "role-badge", + "type": "rectangle", + "x": 320, + "y": 460, + "width": 80, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["role-badge-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "role-badge-text", + "type": "text", + "x": 320, + "y": 464, + "width": 80, + "height": 16, + "text": "Member", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["role-badge-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "accept-button", + "type": "rectangle", + "x": 200, + "y": 540, + "width": 320, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["button-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "accept-button-text", + "type": "text", + "x": 200, + "y": 552, + "width": 320, + "height": 20, + "text": "Accept invitation", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$primary-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["button-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "decline-link-text", + "type": "text", + "x": 320, + "y": 610, + "width": 80, + "height": 20, + "text": "Decline", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["links-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "wrong-account-link-text", + "type": "text", + "x": 260, + "y": 660, + "width": 200, + "height": 20, + "text": "Wrong account?", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["links-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo", + "type": "rectangle", + "x": 1000, + "y": 400, + "width": 160, + "height": 60, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-branding-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo-text", + "type": "text", + "x": 1000, + "y": 420, + "width": 160, + "height": 20, + "text": "MCPGet", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$primary-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-branding-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-tagline", + "type": "text", + "x": 920, + "y": 480, + "width": 320, + "height": 24, + "text": "Discover and install MCPs with ease", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-column-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/medium-fidelity/auth-login.excalidraw b/.context/turbostarter-framework-context/wireframes/medium-fidelity/auth-login.excalidraw new file mode 100644 index 0000000..ef305ca --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/medium-fidelity/auth-login.excalidraw @@ -0,0 +1,711 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "left-column", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["left-column-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-column", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-column-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-placeholder", + "type": "rectangle", + "x": 300, + "y": 80, + "width": 120, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["logo-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 320, + "y": 90, + "width": 80, + "height": 20, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["logo-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-title", + "type": "rectangle", + "x": 200, + "y": 180, + "width": 320, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["title-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-title-text", + "type": "text", + "x": 200, + "y": 180, + "width": 320, + "height": 32, + "text": "Welcome back", + "fontSize": 24, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["title-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-subtitle-text", + "type": "text", + "x": 200, + "y": 216, + "width": 320, + "height": 20, + "text": "Sign in to your account", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["title-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-label", + "type": "text", + "x": 200, + "y": 260, + "width": 100, + "height": 16, + "text": "Email address", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["email-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-input", + "type": "rectangle", + "x": 200, + "y": 280, + "width": 320, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["email-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-placeholder", + "type": "text", + "x": 212, + "y": 292, + "width": 200, + "height": 20, + "text": "you@example.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["email-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-label", + "type": "text", + "x": 200, + "y": 340, + "width": 80, + "height": 16, + "text": "Password", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["password-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-input", + "type": "rectangle", + "x": 200, + "y": 360, + "width": 320, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["password-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-placeholder", + "type": "text", + "x": 212, + "y": 372, + "width": 200, + "height": 20, + "text": "Enter your password", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["password-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-eye-icon", + "type": "ellipse", + "x": 488, + "y": 374, + "width": 16, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["password-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "forgot-password-link", + "type": "text", + "x": 400, + "y": 420, + "width": 120, + "height": 20, + "text": "Forgot password?", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "login-button", + "type": "rectangle", + "x": 200, + "y": 460, + "width": 320, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["button-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "login-button-text", + "type": "text", + "x": 200, + "y": 472, + "width": 320, + "height": 20, + "text": "Sign in", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$primary-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["button-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-line-left", + "type": "rectangle", + "x": 200, + "y": 534, + "width": 130, + "height": 1, + "strokeColor": "$border", + "backgroundColor": "$border", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["divider-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-text", + "type": "text", + "x": 340, + "y": 526, + "width": 40, + "height": 16, + "text": "or", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["divider-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-line-right", + "type": "rectangle", + "x": 390, + "y": 534, + "width": 130, + "height": 1, + "strokeColor": "$border", + "backgroundColor": "$border", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["divider-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-google", + "type": "rectangle", + "x": 200, + "y": 560, + "width": 100, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-google-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-google-text", + "type": "text", + "x": 200, + "y": 572, + "width": 100, + "height": 20, + "text": "G", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-google-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-github", + "type": "rectangle", + "x": 310, + "y": 560, + "width": 100, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-github-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-github-text", + "type": "text", + "x": 310, + "y": 572, + "width": 100, + "height": 20, + "text": "GH", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-github-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-apple", + "type": "rectangle", + "x": 420, + "y": 560, + "width": 100, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-apple-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-apple-text", + "type": "text", + "x": 420, + "y": 572, + "width": 100, + "height": 20, + "text": "A", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-apple-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "signup-link-text", + "type": "text", + "x": 200, + "y": 640, + "width": 320, + "height": 20, + "text": "Don't have an account? Sign up", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo", + "type": "rectangle", + "x": 1000, + "y": 400, + "width": 160, + "height": 60, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-branding-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo-text", + "type": "text", + "x": 1000, + "y": 420, + "width": 160, + "height": 20, + "text": "MCPGet", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$primary-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-branding-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-tagline", + "type": "text", + "x": 920, + "y": 480, + "width": 320, + "height": 24, + "text": "Discover and install MCPs with ease", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-column-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/medium-fidelity/auth-register.excalidraw b/.context/turbostarter-framework-context/wireframes/medium-fidelity/auth-register.excalidraw new file mode 100644 index 0000000..effc3d2 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/medium-fidelity/auth-register.excalidraw @@ -0,0 +1,831 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "left-column", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["left-column-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-column", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-column-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-placeholder", + "type": "rectangle", + "x": 300, + "y": 40, + "width": 120, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["logo-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 320, + "y": 50, + "width": 80, + "height": 20, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["logo-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "create-account-title-text", + "type": "text", + "x": 200, + "y": 100, + "width": 320, + "height": 32, + "text": "Create account", + "fontSize": 24, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["title-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "create-account-subtitle-text", + "type": "text", + "x": 200, + "y": 136, + "width": 320, + "height": 20, + "text": "Get started for free", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["title-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-label", + "type": "text", + "x": 200, + "y": 180, + "width": 80, + "height": 16, + "text": "Full name", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["name-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-input", + "type": "rectangle", + "x": 200, + "y": 200, + "width": 320, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["name-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-placeholder", + "type": "text", + "x": 212, + "y": 212, + "width": 200, + "height": 20, + "text": "John Doe", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["name-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-label", + "type": "text", + "x": 200, + "y": 260, + "width": 100, + "height": 16, + "text": "Email address", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["email-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-input", + "type": "rectangle", + "x": 200, + "y": 280, + "width": 320, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["email-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-placeholder", + "type": "text", + "x": 212, + "y": 292, + "width": 200, + "height": 20, + "text": "you@example.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["email-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-label", + "type": "text", + "x": 200, + "y": 340, + "width": 80, + "height": 16, + "text": "Password", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["password-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-input", + "type": "rectangle", + "x": 200, + "y": 360, + "width": 320, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["password-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-placeholder", + "type": "text", + "x": 212, + "y": 372, + "width": 200, + "height": 20, + "text": "Create a password", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["password-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "confirm-password-label", + "type": "text", + "x": 200, + "y": 420, + "width": 120, + "height": 16, + "text": "Confirm password", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["confirm-password-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "confirm-password-input", + "type": "rectangle", + "x": 200, + "y": 440, + "width": 320, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["confirm-password-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "confirm-password-placeholder", + "type": "text", + "x": 212, + "y": 452, + "width": 200, + "height": 20, + "text": "Confirm your password", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["confirm-password-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "terms-checkbox", + "type": "rectangle", + "x": 200, + "y": 500, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["terms-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "terms-text", + "type": "text", + "x": 230, + "y": 500, + "width": 290, + "height": 20, + "text": "I agree to Terms of Service", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["terms-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "create-account-button", + "type": "rectangle", + "x": 200, + "y": 540, + "width": 320, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["button-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "create-account-button-text", + "type": "text", + "x": 200, + "y": 552, + "width": 320, + "height": 20, + "text": "Create account", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$primary-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["button-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-line-left", + "type": "rectangle", + "x": 200, + "y": 614, + "width": 130, + "height": 1, + "strokeColor": "$border", + "backgroundColor": "$border", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["divider-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-text", + "type": "text", + "x": 340, + "y": 606, + "width": 40, + "height": 16, + "text": "or", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["divider-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-line-right", + "type": "rectangle", + "x": 390, + "y": 614, + "width": 130, + "height": 1, + "strokeColor": "$border", + "backgroundColor": "$border", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["divider-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-google", + "type": "rectangle", + "x": 200, + "y": 640, + "width": 100, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-google-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-google-text", + "type": "text", + "x": 200, + "y": 652, + "width": 100, + "height": 20, + "text": "G", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-google-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-github", + "type": "rectangle", + "x": 310, + "y": 640, + "width": 100, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-github-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-github-text", + "type": "text", + "x": 310, + "y": 652, + "width": 100, + "height": 20, + "text": "GH", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-github-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-apple", + "type": "rectangle", + "x": 420, + "y": 640, + "width": 100, + "height": 44, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-apple-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-apple-text", + "type": "text", + "x": 420, + "y": 652, + "width": 100, + "height": 20, + "text": "A", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["oauth-apple-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "login-link-text", + "type": "text", + "x": 200, + "y": 720, + "width": 320, + "height": 20, + "text": "Already have an account? Sign in", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["form-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo", + "type": "rectangle", + "x": 1000, + "y": 400, + "width": 160, + "height": 60, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-branding-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo-text", + "type": "text", + "x": 1000, + "y": 420, + "width": 160, + "height": 20, + "text": "MCPGet", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$primary-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-branding-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-tagline", + "type": "text", + "x": 920, + "y": 480, + "width": 320, + "height": 24, + "text": "Discover and install MCPs with ease", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["right-column-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/medium-fidelity/dashboard-admin.excalidraw b/.context/turbostarter-framework-context/wireframes/medium-fidelity/dashboard-admin.excalidraw new file mode 100644 index 0000000..3a7ca3f --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/medium-fidelity/dashboard-admin.excalidraw @@ -0,0 +1,1681 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-text", + "type": "text", + "x": 32, + "y": 24, + "width": 96, + "height": 18, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-home", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-home-icon", + "type": "rectangle", + "x": 32, + "y": 110, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-home-text", + "type": "text", + "x": 64, + "y": 112, + "width": 50, + "height": 16, + "text": "Home", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-icon", + "type": "rectangle", + "x": 32, + "y": 170, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-text", + "type": "text", + "x": 64, + "y": 172, + "width": 50, + "height": 16, + "text": "Users", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-orgs", + "type": "rectangle", + "x": 20, + "y": 220, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-orgs-icon", + "type": "rectangle", + "x": 32, + "y": 230, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-orgs-text", + "type": "text", + "x": 64, + "y": 232, + "width": 100, + "height": 16, + "text": "Organizations", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-customers", + "type": "rectangle", + "x": 20, + "y": 280, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-customers-icon", + "type": "rectangle", + "x": 32, + "y": 290, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-customers-text", + "type": "text", + "x": 64, + "y": 292, + "width": 80, + "height": 16, + "text": "Customers", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-avatar", + "type": "rectangle", + "x": 28, + "y": 858, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-name", + "type": "text", + "x": 56, + "y": 860, + "width": 80, + "height": 14, + "text": "Admin", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "text", + "x": 304, + "y": 20, + "width": 160, + "height": 24, + "text": "Admin Dashboard", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-user-avatar", + "type": "rectangle", + "x": 1388, + "y": 16, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 16 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 264, + "height": 100, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-label", + "type": "text", + "x": 324, + "y": 108, + "width": 80, + "height": 16, + "text": "Total Users", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-value", + "type": "text", + "x": 324, + "y": 136, + "width": 100, + "height": 36, + "text": "1,234", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2", + "type": "rectangle", + "x": 584, + "y": 88, + "width": 264, + "height": 100, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-label", + "type": "text", + "x": 604, + "y": 108, + "width": 80, + "height": 16, + "text": "Organizations", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-value", + "type": "text", + "x": 604, + "y": 136, + "width": 60, + "height": 36, + "text": "56", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3", + "type": "rectangle", + "x": 864, + "y": 88, + "width": 264, + "height": 100, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-label", + "type": "text", + "x": 884, + "y": 108, + "width": 60, + "height": 16, + "text": "Revenue", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-value", + "type": "text", + "x": 884, + "y": 136, + "width": 100, + "height": 36, + "text": "$12.4k", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$success", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4", + "type": "rectangle", + "x": 1144, + "y": 88, + "width": 264, + "height": 100, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-label", + "type": "text", + "x": 1164, + "y": 108, + "width": 100, + "height": 16, + "text": "Sessions", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-value", + "type": "text", + "x": 1164, + "y": 136, + "width": 80, + "height": 36, + "text": "342", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-card", + "type": "rectangle", + "x": 304, + "y": 208, + "width": 720, + "height": 440, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-card-title", + "type": "text", + "x": 324, + "y": 228, + "width": 140, + "height": 22, + "text": "Recent Activity", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-1", + "type": "rectangle", + "x": 324, + "y": 268, + "width": 680, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-1-avatar", + "type": "rectangle", + "x": 340, + "y": 280, + "width": 24, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-1-text", + "type": "text", + "x": 376, + "y": 284, + "width": 300, + "height": 16, + "text": "User signed up - john@example.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-1-time", + "type": "text", + "x": 924, + "y": 284, + "width": 60, + "height": 16, + "text": "2m ago", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-2", + "type": "rectangle", + "x": 324, + "y": 328, + "width": 680, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-2-avatar", + "type": "rectangle", + "x": 340, + "y": 340, + "width": 24, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-2-text", + "type": "text", + "x": 376, + "y": 344, + "width": 280, + "height": 16, + "text": "New org created - Acme Corp", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-2-time", + "type": "text", + "x": 924, + "y": 344, + "width": 60, + "height": 16, + "text": "5m ago", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-3", + "type": "rectangle", + "x": 324, + "y": 388, + "width": 680, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-3-avatar", + "type": "rectangle", + "x": 340, + "y": 400, + "width": 24, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-3-text", + "type": "text", + "x": 376, + "y": 404, + "width": 320, + "height": 16, + "text": "Subscription upgraded - Pro Plan", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-3-time", + "type": "text", + "x": 924, + "y": 404, + "width": 60, + "height": 16, + "text": "12m ago", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-4", + "type": "rectangle", + "x": 324, + "y": 448, + "width": 680, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-4-avatar", + "type": "rectangle", + "x": 340, + "y": 460, + "width": 24, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-4-text", + "type": "text", + "x": 376, + "y": 464, + "width": 260, + "height": 16, + "text": "User invited - team@acme.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-4-time", + "type": "text", + "x": 924, + "y": 464, + "width": 60, + "height": 16, + "text": "25m ago", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-5", + "type": "rectangle", + "x": 324, + "y": 508, + "width": 680, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-5-avatar", + "type": "rectangle", + "x": 340, + "y": 520, + "width": 24, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-5-text", + "type": "text", + "x": 376, + "y": 524, + "width": 340, + "height": 16, + "text": "API rate limit exceeded - user_123", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-5-time", + "type": "text", + "x": 924, + "y": 524, + "width": 60, + "height": 16, + "text": "1h ago", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-6", + "type": "rectangle", + "x": 324, + "y": 568, + "width": 680, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-6-avatar", + "type": "rectangle", + "x": 340, + "y": 580, + "width": 24, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-6-text", + "type": "text", + "x": 376, + "y": 584, + "width": 290, + "height": 16, + "text": "New feature deployed - v2.1.0", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-6-time", + "type": "text", + "x": 924, + "y": 584, + "width": 60, + "height": 16, + "text": "2h ago", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-actions-card", + "type": "rectangle", + "x": 1044, + "y": 208, + "width": 364, + "height": 440, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-actions-title", + "type": "text", + "x": 1064, + "y": 228, + "width": 120, + "height": 22, + "text": "Quick Actions", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-1", + "type": "rectangle", + "x": 1064, + "y": 268, + "width": 324, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-1-text", + "type": "text", + "x": 1184, + "y": 284, + "width": 84, + "height": 16, + "text": "Add User", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-2", + "type": "rectangle", + "x": 1064, + "y": 332, + "width": 324, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-2-text", + "type": "text", + "x": 1172, + "y": 348, + "width": 108, + "height": 16, + "text": "View Reports", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-3", + "type": "rectangle", + "x": 1064, + "y": 396, + "width": 324, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-3-text", + "type": "text", + "x": 1188, + "y": 412, + "width": 76, + "height": 16, + "text": "Settings", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-4", + "type": "rectangle", + "x": 1064, + "y": 460, + "width": 324, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-4-text", + "type": "text", + "x": 1176, + "y": 476, + "width": 100, + "height": 16, + "text": "Audit Logs", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-5", + "type": "rectangle", + "x": 1064, + "y": 524, + "width": 324, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-5-text", + "type": "text", + "x": 1156, + "y": 540, + "width": 140, + "height": 16, + "text": "System Maintenance", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/medium-fidelity/dashboard-org.excalidraw b/.context/turbostarter-framework-context/wireframes/medium-fidelity/dashboard-org.excalidraw new file mode 100644 index 0000000..a5fbfec --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/medium-fidelity/dashboard-org.excalidraw @@ -0,0 +1,1546 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-text", + "type": "text", + "x": 32, + "y": 24, + "width": 96, + "height": 18, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-overview", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-overview-icon", + "type": "rectangle", + "x": 32, + "y": 110, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-overview-text", + "type": "text", + "x": 64, + "y": 112, + "width": 80, + "height": 16, + "text": "Overview", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-icon", + "type": "rectangle", + "x": 32, + "y": 170, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-text", + "type": "text", + "x": 64, + "y": 172, + "width": 70, + "height": 16, + "text": "Members", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings", + "type": "rectangle", + "x": 20, + "y": 220, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-icon", + "type": "rectangle", + "x": 32, + "y": 230, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-text", + "type": "text", + "x": 64, + "y": 232, + "width": 60, + "height": 16, + "text": "Settings", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing", + "type": "rectangle", + "x": 20, + "y": 280, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing-icon", + "type": "rectangle", + "x": 32, + "y": 290, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing-text", + "type": "text", + "x": 64, + "y": 292, + "width": 50, + "height": 16, + "text": "Billing", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-avatar", + "type": "rectangle", + "x": 28, + "y": 858, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-name", + "type": "text", + "x": 56, + "y": 860, + "width": 80, + "height": 14, + "text": "Acme Inc.", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "text", + "x": 304, + "y": 20, + "width": 200, + "height": 24, + "text": "Organization Overview", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-user-avatar", + "type": "rectangle", + "x": 1388, + "y": 16, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 16 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 264, + "height": 100, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-label", + "type": "text", + "x": 324, + "y": 108, + "width": 100, + "height": 16, + "text": "Total Members", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-value", + "type": "text", + "x": 324, + "y": 136, + "width": 80, + "height": 36, + "text": "24", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2", + "type": "rectangle", + "x": 584, + "y": 88, + "width": 264, + "height": 100, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-label", + "type": "text", + "x": 604, + "y": 108, + "width": 100, + "height": 16, + "text": "Active Users", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-value", + "type": "text", + "x": 604, + "y": 136, + "width": 60, + "height": 36, + "text": "18", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3", + "type": "rectangle", + "x": 864, + "y": 88, + "width": 264, + "height": 100, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-label", + "type": "text", + "x": 884, + "y": 108, + "width": 80, + "height": 16, + "text": "API Calls", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-value", + "type": "text", + "x": 884, + "y": 136, + "width": 100, + "height": 36, + "text": "12.5k", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4", + "type": "rectangle", + "x": 1144, + "y": 88, + "width": 264, + "height": 100, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-label", + "type": "text", + "x": 1164, + "y": 108, + "width": 100, + "height": 16, + "text": "Storage", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-value", + "type": "text", + "x": 1164, + "y": 136, + "width": 80, + "height": 36, + "text": "2.4 GB", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-card", + "type": "rectangle", + "x": 304, + "y": 208, + "width": 1104, + "height": 320, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-title", + "type": "text", + "x": 324, + "y": 228, + "width": 140, + "height": 22, + "text": "Usage Over Time", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-area", + "type": "rectangle", + "x": 324, + "y": 268, + "width": 1064, + "height": 240, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-line-1", + "type": "rectangle", + "x": 324, + "y": 380, + "width": 1064, + "height": 4, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-line-2", + "type": "rectangle", + "x": 324, + "y": 340, + "width": 1064, + "height": 4, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-line-3", + "type": "rectangle", + "x": 324, + "y": 420, + "width": 1064, + "height": 4, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-line-4", + "type": "rectangle", + "x": 324, + "y": 460, + "width": 1064, + "height": 4, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-axis-jan", + "type": "text", + "x": 364, + "y": 488, + "width": 30, + "height": 12, + "text": "Jan", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-axis-feb", + "type": "text", + "x": 544, + "y": 488, + "width": 30, + "height": 12, + "text": "Feb", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-axis-mar", + "type": "text", + "x": 724, + "y": 488, + "width": 30, + "height": 12, + "text": "Mar", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-axis-apr", + "type": "text", + "x": 904, + "y": 488, + "width": 30, + "height": 12, + "text": "Apr", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-axis-may", + "type": "text", + "x": 1084, + "y": 488, + "width": 30, + "height": 12, + "text": "May", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-axis-jun", + "type": "text", + "x": 1264, + "y": 488, + "width": 30, + "height": 12, + "text": "Jun", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1", + "type": "rectangle", + "x": 304, + "y": 548, + "width": 540, + "height": 260, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-title", + "type": "text", + "x": 324, + "y": 568, + "width": 100, + "height": 22, + "text": "By Category", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-area", + "type": "rectangle", + "x": 324, + "y": 608, + "width": 500, + "height": 180, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-1", + "type": "rectangle", + "x": 364, + "y": 688, + "width": 40, + "height": 80, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-2", + "type": "rectangle", + "x": 444, + "y": 648, + "width": 40, + "height": 120, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-3", + "type": "rectangle", + "x": 524, + "y": 708, + "width": 40, + "height": 60, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-4", + "type": "rectangle", + "x": 604, + "y": 628, + "width": 40, + "height": 140, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-5", + "type": "rectangle", + "x": 684, + "y": 668, + "width": 40, + "height": 100, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-6", + "type": "rectangle", + "x": 764, + "y": 698, + "width": 40, + "height": 70, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2", + "type": "rectangle", + "x": 864, + "y": 548, + "width": 544, + "height": 260, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-title", + "type": "text", + "x": 884, + "y": 568, + "width": 120, + "height": 22, + "text": "Distribution", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-area", + "type": "rectangle", + "x": 884, + "y": 608, + "width": 504, + "height": 180, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-pie", + "type": "rectangle", + "x": 1036, + "y": 628, + "width": 140, + "height": 140, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 70 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-pie-inner", + "type": "rectangle", + "x": 1071, + "y": 663, + "width": 70, + "height": 70, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 35 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-legend-1", + "type": "rectangle", + "x": 1220, + "y": 648, + "width": 12, + "height": 12, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-legend-1-text", + "type": "text", + "x": 1240, + "y": 648, + "width": 60, + "height": 12, + "text": "Chat 45%", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-legend-2", + "type": "rectangle", + "x": 1220, + "y": 672, + "width": 12, + "height": 12, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-legend-2-text", + "type": "text", + "x": 1240, + "y": 672, + "width": 70, + "height": 12, + "text": "Image 35%", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-legend-3", + "type": "rectangle", + "x": 1220, + "y": 696, + "width": 12, + "height": 12, + "strokeColor": "$border", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-legend-3-text", + "type": "text", + "x": 1240, + "y": 696, + "width": 60, + "height": 12, + "text": "PDF 20%", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/medium-fidelity/dashboard-user.excalidraw b/.context/turbostarter-framework-context/wireframes/medium-fidelity/dashboard-user.excalidraw new file mode 100644 index 0000000..6fc3698 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/medium-fidelity/dashboard-user.excalidraw @@ -0,0 +1,1186 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-text", + "type": "text", + "x": 32, + "y": 24, + "width": 96, + "height": 18, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-icon", + "type": "rectangle", + "x": 32, + "y": 110, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-text", + "type": "text", + "x": 64, + "y": 112, + "width": 80, + "height": 16, + "text": "Dashboard", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-aitools", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-aitools-icon", + "type": "rectangle", + "x": 32, + "y": 170, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-aitools-text", + "type": "text", + "x": 64, + "y": 172, + "width": 60, + "height": 16, + "text": "AI Tools", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings", + "type": "rectangle", + "x": 20, + "y": 220, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-icon", + "type": "rectangle", + "x": 32, + "y": 230, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-text", + "type": "text", + "x": 64, + "y": 232, + "width": 60, + "height": 16, + "text": "Settings", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-support-item", + "type": "rectangle", + "x": 20, + "y": 720, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-support-icon", + "type": "rectangle", + "x": 32, + "y": 730, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-support-text", + "type": "text", + "x": 64, + "y": 732, + "width": 60, + "height": 16, + "text": "Support", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-feedback-item", + "type": "rectangle", + "x": 20, + "y": 780, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-feedback-icon", + "type": "rectangle", + "x": 32, + "y": 790, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-feedback-text", + "type": "text", + "x": 64, + "y": 792, + "width": 70, + "height": 16, + "text": "Feedback", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-avatar", + "type": "rectangle", + "x": 28, + "y": 858, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-name", + "type": "text", + "x": 56, + "y": 860, + "width": 80, + "height": 14, + "text": "John Doe", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "text", + "x": 304, + "y": 20, + "width": 120, + "height": 24, + "text": "Dashboard", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-user-avatar", + "type": "rectangle", + "x": 1388, + "y": 16, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 16 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-card", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 1112, + "height": 120, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-title", + "type": "text", + "x": 324, + "y": 108, + "width": 200, + "height": 28, + "text": "Welcome back!", + "fontSize": 24, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-subtitle", + "type": "text", + "x": 324, + "y": 148, + "width": 400, + "height": 18, + "text": "Get started with our AI tools", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-subtitle-2", + "type": "text", + "x": 324, + "y": 172, + "width": 300, + "height": 14, + "text": "Explore chat, image generation, and PDF tools below.", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1", + "type": "rectangle", + "x": 304, + "y": 228, + "width": 360, + "height": 220, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-icon", + "type": "rectangle", + "x": 324, + "y": 248, + "width": 48, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-title", + "type": "text", + "x": 324, + "y": 316, + "width": 100, + "height": 22, + "text": "AI Chat", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-desc", + "type": "text", + "x": 324, + "y": 348, + "width": 320, + "height": 18, + "text": "Start conversations with AI", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-desc-2", + "type": "text", + "x": 324, + "y": 372, + "width": 280, + "height": 14, + "text": "Ask questions, get help, brainstorm ideas.", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-button", + "type": "rectangle", + "x": 324, + "y": 404, + "width": 100, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-button-text", + "type": "text", + "x": 352, + "y": 414, + "width": 44, + "height": 16, + "text": "Open", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2", + "type": "rectangle", + "x": 680, + "y": 228, + "width": 360, + "height": 220, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-icon", + "type": "rectangle", + "x": 700, + "y": 248, + "width": 48, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-title", + "type": "text", + "x": 700, + "y": 316, + "width": 160, + "height": 22, + "text": "Image Generation", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-desc", + "type": "text", + "x": 700, + "y": 348, + "width": 320, + "height": 18, + "text": "Create images from text", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-desc-2", + "type": "text", + "x": 700, + "y": 372, + "width": 260, + "height": 14, + "text": "Generate stunning visuals with AI.", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-button", + "type": "rectangle", + "x": 700, + "y": 404, + "width": 100, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-button-text", + "type": "text", + "x": 720, + "y": 414, + "width": 60, + "height": 16, + "text": "Try Now", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3", + "type": "rectangle", + "x": 1056, + "y": 228, + "width": 360, + "height": 220, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-icon", + "type": "rectangle", + "x": 1076, + "y": 248, + "width": 48, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-title", + "type": "text", + "x": 1076, + "y": 316, + "width": 100, + "height": 22, + "text": "PDF Tools", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-desc", + "type": "text", + "x": 1076, + "y": 348, + "width": 320, + "height": 18, + "text": "Chat with your documents", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-desc-2", + "type": "text", + "x": 1076, + "y": 372, + "width": 280, + "height": 14, + "text": "Upload PDFs and ask questions about them.", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-button", + "type": "rectangle", + "x": 1076, + "y": 404, + "width": 100, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-button-text", + "type": "text", + "x": 1100, + "y": 414, + "width": 52, + "height": 16, + "text": "Upload", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/medium-fidelity/data-table-invitations.excalidraw b/.context/turbostarter-framework-context/wireframes/medium-fidelity/data-table-invitations.excalidraw new file mode 100644 index 0000000..df1a10b --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/medium-fidelity/data-table-invitations.excalidraw @@ -0,0 +1,1756 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-switcher", + "type": "rectangle", + "x": 20, + "y": 80, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-avatar", + "type": "rectangle", + "x": 32, + "y": 88, + "width": 24, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-name", + "type": "text", + "x": 64, + "y": 92, + "width": 100, + "height": 16, + "text": "Acme Inc", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-section-label", + "type": "text", + "x": 20, + "y": 140, + "width": 80, + "height": 12, + "text": "WORKSPACE", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-label", + "type": "text", + "x": 60, + "y": 172, + "width": 80, + "height": 16, + "text": "Dashboard", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-active", + "type": "rectangle", + "x": 20, + "y": 208, + "width": 240, + "height": 40, + "strokeColor": "$primary", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-icon", + "type": "rectangle", + "x": 32, + "y": 218, + "width": 20, + "height": 20, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-label", + "type": "text", + "x": 60, + "y": 220, + "width": 60, + "height": 16, + "text": "Members", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings", + "type": "rectangle", + "x": 20, + "y": 256, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-label", + "type": "text", + "x": 60, + "y": 268, + "width": 60, + "height": 16, + "text": "Settings", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing", + "type": "rectangle", + "x": 20, + "y": 304, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing-label", + "type": "text", + "x": 60, + "y": 316, + "width": 50, + "height": 16, + "text": "Billing", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "text", + "x": 304, + "y": 20, + "width": 80, + "height": 24, + "text": "Members", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invite-button", + "type": "rectangle", + "x": 1276, + "y": 16, + "width": 140, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invite-text", + "type": "text", + "x": 1296, + "y": 24, + "width": 100, + "height": 16, + "text": "Invite Member", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tabs-container", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 1112, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-members", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 120, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-members-label", + "type": "text", + "x": 316, + "y": 104, + "width": 96, + "height": 16, + "text": "Members (24)", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["tabs-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-invitations-active", + "type": "rectangle", + "x": 424, + "y": 88, + "width": 180, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-invitations-label", + "type": "text", + "x": 436, + "y": 104, + "width": 156, + "height": 16, + "text": "Pending Invitations (3)", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-invitations-underline", + "type": "rectangle", + "x": 424, + "y": 132, + "width": 180, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-container", + "type": "rectangle", + "x": 304, + "y": 152, + "width": 1112, + "height": 716, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "toolbar", + "type": "rectangle", + "x": 304, + "y": 152, + "width": 1112, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-input", + "type": "rectangle", + "x": 316, + "y": 162, + "width": 240, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-placeholder", + "type": "text", + "x": 336, + "y": 172, + "width": 160, + "height": 16, + "text": "Search invitations...", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-status", + "type": "rectangle", + "x": 568, + "y": 162, + "width": 100, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-status-text", + "type": "text", + "x": 588, + "y": 172, + "width": 50, + "height": 16, + "text": "Status", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-status-arrow", + "type": "rectangle", + "x": 648, + "y": 176, + "width": 8, + "height": 8, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-header-row", + "type": "rectangle", + "x": 304, + "y": 208, + "width": 1112, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-email-col", + "type": "text", + "x": 320, + "y": 224, + "width": 50, + "height": 14, + "text": "Email", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-role-col", + "type": "text", + "x": 480, + "y": 224, + "width": 40, + "height": 14, + "text": "Role", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invitedby-col", + "type": "text", + "x": 600, + "y": 224, + "width": 70, + "height": 14, + "text": "Invited by", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-sent-col", + "type": "text", + "x": 740, + "y": 224, + "width": 40, + "height": 14, + "text": "Sent", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-expires-col", + "type": "text", + "x": 860, + "y": 224, + "width": 55, + "height": 14, + "text": "Expires", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-status-col", + "type": "text", + "x": 980, + "y": 224, + "width": 50, + "height": 14, + "text": "Status", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-actions-col", + "type": "text", + "x": 1120, + "y": 224, + "width": 55, + "height": 14, + "text": "Actions", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-1", + "type": "rectangle", + "x": 304, + "y": 256, + "width": 1112, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-email", + "type": "text", + "x": 320, + "y": 277, + "width": 140, + "height": 14, + "text": "alice@example.com", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-badge", + "type": "rectangle", + "x": 480, + "y": 274, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-text", + "type": "text", + "x": 490, + "y": 277, + "width": 45, + "height": 14, + "text": "Member", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-invitedby", + "type": "text", + "x": 600, + "y": 277, + "width": 80, + "height": 14, + "text": "John Doe", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-sent", + "type": "text", + "x": 740, + "y": 277, + "width": 50, + "height": 14, + "text": "Jan 20", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-expires", + "type": "text", + "x": 860, + "y": 277, + "width": 50, + "height": 14, + "text": "Jan 27", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status-badge", + "type": "rectangle", + "x": 980, + "y": 274, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status-text", + "type": "text", + "x": 990, + "y": 277, + "width": 45, + "height": 14, + "text": "Pending", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-resend", + "type": "rectangle", + "x": 1120, + "y": 274, + "width": 60, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-resend-text", + "type": "text", + "x": 1130, + "y": 277, + "width": 40, + "height": 14, + "text": "Resend", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-cancel", + "type": "rectangle", + "x": 1188, + "y": 274, + "width": 60, + "height": 20, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-cancel-text", + "type": "text", + "x": 1198, + "y": 277, + "width": 40, + "height": 14, + "text": "Cancel", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-2", + "type": "rectangle", + "x": 304, + "y": 312, + "width": 1112, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-email", + "type": "text", + "x": 320, + "y": 333, + "width": 150, + "height": 14, + "text": "charlie@example.com", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-badge", + "type": "rectangle", + "x": 480, + "y": 330, + "width": 55, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-text", + "type": "text", + "x": 492, + "y": 333, + "width": 35, + "height": 14, + "text": "Admin", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-invitedby", + "type": "text", + "x": 600, + "y": 333, + "width": 80, + "height": 14, + "text": "Jane Smith", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-sent", + "type": "text", + "x": 740, + "y": 333, + "width": 50, + "height": 14, + "text": "Jan 15", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-expires", + "type": "text", + "x": 860, + "y": 333, + "width": 50, + "height": 14, + "text": "Jan 22", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status-badge", + "type": "rectangle", + "x": 980, + "y": 330, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status-text", + "type": "text", + "x": 990, + "y": 333, + "width": 45, + "height": 14, + "text": "Expired", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-resend", + "type": "rectangle", + "x": 1120, + "y": 330, + "width": 60, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-resend-text", + "type": "text", + "x": 1130, + "y": 333, + "width": 40, + "height": 14, + "text": "Resend", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-cancel", + "type": "rectangle", + "x": 1188, + "y": 330, + "width": 60, + "height": 20, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-cancel-text", + "type": "text", + "x": 1198, + "y": 333, + "width": 40, + "height": 14, + "text": "Cancel", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "empty-rows-area", + "type": "rectangle", + "x": 304, + "y": 368, + "width": 1112, + "height": 452, + "strokeColor": "transparent", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-bar", + "type": "rectangle", + "x": 304, + "y": 820, + "width": 1112, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-info", + "type": "text", + "x": 328, + "y": 836, + "width": 110, + "height": 16, + "text": "Showing 1-3 of 3", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-prev", + "type": "rectangle", + "x": 1284, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-1", + "type": "rectangle", + "x": 1324, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-1-text", + "type": "text", + "x": 1336, + "y": 838, + "width": 10, + "height": 12, + "text": "1", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-next", + "type": "rectangle", + "x": 1364, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/medium-fidelity/data-table-members.excalidraw b/.context/turbostarter-framework-context/wireframes/medium-fidelity/data-table-members.excalidraw new file mode 100644 index 0000000..4b47e4a --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/medium-fidelity/data-table-members.excalidraw @@ -0,0 +1,1726 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-switcher", + "type": "rectangle", + "x": 20, + "y": 80, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-avatar", + "type": "rectangle", + "x": 32, + "y": 88, + "width": 24, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-name", + "type": "text", + "x": 64, + "y": 92, + "width": 100, + "height": 16, + "text": "Acme Inc", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-section-label", + "type": "text", + "x": 20, + "y": 140, + "width": 80, + "height": 12, + "text": "WORKSPACE", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-label", + "type": "text", + "x": 60, + "y": 172, + "width": 80, + "height": 16, + "text": "Dashboard", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-active", + "type": "rectangle", + "x": 20, + "y": 208, + "width": 240, + "height": 40, + "strokeColor": "$primary", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-icon", + "type": "rectangle", + "x": 32, + "y": 218, + "width": 20, + "height": 20, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-label", + "type": "text", + "x": 60, + "y": 220, + "width": 60, + "height": 16, + "text": "Members", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings", + "type": "rectangle", + "x": 20, + "y": 256, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-label", + "type": "text", + "x": 60, + "y": 268, + "width": 60, + "height": 16, + "text": "Settings", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing", + "type": "rectangle", + "x": 20, + "y": 304, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing-label", + "type": "text", + "x": 60, + "y": 316, + "width": 50, + "height": 16, + "text": "Billing", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "text", + "x": 304, + "y": 20, + "width": 80, + "height": 24, + "text": "Members", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invite-button", + "type": "rectangle", + "x": 1276, + "y": 16, + "width": 140, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invite-text", + "type": "text", + "x": 1296, + "y": 24, + "width": 100, + "height": 16, + "text": "Invite Member", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tabs-container", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 1112, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-members-active", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 120, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-members-label", + "type": "text", + "x": 316, + "y": 104, + "width": 96, + "height": 16, + "text": "Members (24)", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-members-underline", + "type": "rectangle", + "x": 304, + "y": 132, + "width": 120, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-invitations", + "type": "rectangle", + "x": 424, + "y": 88, + "width": 180, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-invitations-label", + "type": "text", + "x": 436, + "y": 104, + "width": 156, + "height": 16, + "text": "Pending Invitations (3)", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["tabs-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-container", + "type": "rectangle", + "x": 304, + "y": 152, + "width": 1112, + "height": 716, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "toolbar", + "type": "rectangle", + "x": 304, + "y": 152, + "width": 1112, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-input", + "type": "rectangle", + "x": 316, + "y": 162, + "width": 240, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-placeholder", + "type": "text", + "x": 336, + "y": 172, + "width": 140, + "height": 16, + "text": "Search members...", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role", + "type": "rectangle", + "x": 568, + "y": 162, + "width": 100, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role-text", + "type": "text", + "x": 588, + "y": 172, + "width": 40, + "height": 16, + "text": "Role", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role-arrow", + "type": "rectangle", + "x": 648, + "y": 176, + "width": 8, + "height": 8, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-header-row", + "type": "rectangle", + "x": 304, + "y": 208, + "width": 1112, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-member-col", + "type": "text", + "x": 320, + "y": 224, + "width": 60, + "height": 14, + "text": "Member", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-role-col", + "type": "text", + "x": 600, + "y": 224, + "width": 40, + "height": 14, + "text": "Role", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-joined-col", + "type": "text", + "x": 880, + "y": 224, + "width": 50, + "height": 14, + "text": "Joined", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-actions-col", + "type": "text", + "x": 1340, + "y": 224, + "width": 55, + "height": 14, + "text": "Actions", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-1", + "type": "rectangle", + "x": 304, + "y": 256, + "width": 1112, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-avatar", + "type": "rectangle", + "x": 320, + "y": 274, + "width": 28, + "height": 28, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 14 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-name", + "type": "text", + "x": 360, + "y": 270, + "width": 80, + "height": 14, + "text": "John Doe", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-email", + "type": "text", + "x": 360, + "y": 286, + "width": 130, + "height": 12, + "text": "john@example.com", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-dropdown", + "type": "rectangle", + "x": 600, + "y": 276, + "width": 100, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-text", + "type": "text", + "x": 612, + "y": 281, + "width": 50, + "height": 14, + "text": "Owner", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-arrow", + "type": "rectangle", + "x": 680, + "y": 284, + "width": 8, + "height": 8, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-joined", + "type": "text", + "x": 880, + "y": 281, + "width": 90, + "height": 14, + "text": "Jan 1, 2024", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-remove", + "type": "rectangle", + "x": 1356, + "y": 278, + "width": 24, + "height": 20, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-2", + "type": "rectangle", + "x": 304, + "y": 320, + "width": 1112, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-avatar", + "type": "rectangle", + "x": 320, + "y": 338, + "width": 28, + "height": 28, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 14 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-name", + "type": "text", + "x": 360, + "y": 334, + "width": 90, + "height": 14, + "text": "Jane Smith", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-email", + "type": "text", + "x": 360, + "y": 350, + "width": 130, + "height": 12, + "text": "jane@example.com", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-dropdown", + "type": "rectangle", + "x": 600, + "y": 340, + "width": 100, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-text", + "type": "text", + "x": 612, + "y": 345, + "width": 50, + "height": 14, + "text": "Admin", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-arrow", + "type": "rectangle", + "x": 680, + "y": 348, + "width": 8, + "height": 8, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-joined", + "type": "text", + "x": 880, + "y": 345, + "width": 90, + "height": 14, + "text": "Jan 5, 2024", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-remove", + "type": "rectangle", + "x": 1356, + "y": 342, + "width": 24, + "height": 20, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-3", + "type": "rectangle", + "x": 304, + "y": 384, + "width": 1112, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-avatar", + "type": "rectangle", + "x": 320, + "y": 402, + "width": 28, + "height": 28, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 14 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-name", + "type": "text", + "x": 360, + "y": 398, + "width": 90, + "height": 14, + "text": "Bob Wilson", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-email", + "type": "text", + "x": 360, + "y": 414, + "width": 130, + "height": 12, + "text": "bob@example.com", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-dropdown", + "type": "rectangle", + "x": 600, + "y": 404, + "width": 100, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-text", + "type": "text", + "x": 612, + "y": 409, + "width": 50, + "height": 14, + "text": "Member", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-arrow", + "type": "rectangle", + "x": 680, + "y": 412, + "width": 8, + "height": 8, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-joined", + "type": "text", + "x": 880, + "y": 409, + "width": 95, + "height": 14, + "text": "Jan 10, 2024", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-remove", + "type": "rectangle", + "x": 1356, + "y": 406, + "width": 24, + "height": 20, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "empty-rows-area", + "type": "rectangle", + "x": 304, + "y": 448, + "width": 1112, + "height": 372, + "strokeColor": "transparent", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-bar", + "type": "rectangle", + "x": 304, + "y": 820, + "width": 1112, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-info", + "type": "text", + "x": 328, + "y": 836, + "width": 130, + "height": 16, + "text": "Showing 1-10 of 24", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-prev", + "type": "rectangle", + "x": 1244, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-1", + "type": "rectangle", + "x": 1284, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-1-text", + "type": "text", + "x": 1296, + "y": 838, + "width": 10, + "height": 12, + "text": "1", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-2", + "type": "rectangle", + "x": 1324, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-2-text", + "type": "text", + "x": 1336, + "y": 838, + "width": 10, + "height": 12, + "text": "2", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-next", + "type": "rectangle", + "x": 1364, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/medium-fidelity/data-table-users.excalidraw b/.context/turbostarter-framework-context/wireframes/medium-fidelity/data-table-users.excalidraw new file mode 100644 index 0000000..c3343eb --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/medium-fidelity/data-table-users.excalidraw @@ -0,0 +1,2086 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-section-label", + "type": "text", + "x": 20, + "y": 80, + "width": 80, + "height": 14, + "text": "ADMIN", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-label", + "type": "text", + "x": 60, + "y": 112, + "width": 80, + "height": 16, + "text": "Dashboard", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-active", + "type": "rectangle", + "x": 20, + "y": 148, + "width": 240, + "height": 40, + "strokeColor": "$primary", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-icon", + "type": "rectangle", + "x": 32, + "y": 158, + "width": 20, + "height": 20, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-label", + "type": "text", + "x": 60, + "y": 160, + "width": 50, + "height": 16, + "text": "Users", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings", + "type": "rectangle", + "x": 20, + "y": 196, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-label", + "type": "text", + "x": 60, + "y": 208, + "width": 60, + "height": 16, + "text": "Settings", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-analytics", + "type": "rectangle", + "x": 20, + "y": 244, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-analytics-label", + "type": "text", + "x": 60, + "y": 256, + "width": 70, + "height": 16, + "text": "Analytics", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "text", + "x": 304, + "y": 20, + "width": 60, + "height": 24, + "text": "Users", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-count-badge", + "type": "rectangle", + "x": 372, + "y": 20, + "width": 80, + "height": 24, + "strokeColor": "transparent", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-count-text", + "type": "text", + "x": 382, + "y": 24, + "width": 60, + "height": 16, + "text": "1,234 users", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-container", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 1112, + "height": 780, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "toolbar", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 1112, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-input", + "type": "rectangle", + "x": 316, + "y": 98, + "width": 200, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-placeholder", + "type": "text", + "x": 336, + "y": 108, + "width": 120, + "height": 16, + "text": "Search users...", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role", + "type": "rectangle", + "x": 528, + "y": 98, + "width": 80, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role-text", + "type": "text", + "x": 540, + "y": 108, + "width": 40, + "height": 16, + "text": "Role", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-2fa", + "type": "rectangle", + "x": 620, + "y": 98, + "width": 70, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-2fa-text", + "type": "text", + "x": 635, + "y": 108, + "width": 30, + "height": 16, + "text": "2FA", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-status", + "type": "rectangle", + "x": 702, + "y": 98, + "width": 80, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-status-text", + "type": "text", + "x": 714, + "y": 108, + "width": 50, + "height": 16, + "text": "Status", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-date", + "type": "rectangle", + "x": 794, + "y": 98, + "width": 80, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-date-text", + "type": "text", + "x": 812, + "y": 108, + "width": 40, + "height": 16, + "text": "Date", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "view-options", + "type": "rectangle", + "x": 1316, + "y": 98, + "width": 88, + "height": 36, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "toolbar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "view-options-text", + "type": "text", + "x": 1326, + "y": 108, + "width": 60, + "height": 16, + "text": "Columns", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "toolbar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-header-row", + "type": "rectangle", + "x": 304, + "y": 144, + "width": 1112, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-checkbox", + "type": "rectangle", + "x": 320, + "y": 158, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "header-row-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-user-col", + "type": "text", + "x": 360, + "y": 160, + "width": 40, + "height": 14, + "text": "User", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-role-col", + "type": "text", + "x": 580, + "y": 160, + "width": 40, + "height": 14, + "text": "Role", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-2fa-col", + "type": "text", + "x": 700, + "y": 160, + "width": 30, + "height": 14, + "text": "2FA", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-status-col", + "type": "text", + "x": 820, + "y": 160, + "width": 50, + "height": 14, + "text": "Status", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-created-col", + "type": "text", + "x": 980, + "y": 160, + "width": 60, + "height": 14, + "text": "Created", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-actions-col", + "type": "text", + "x": 1340, + "y": 160, + "width": 55, + "height": 14, + "text": "Actions", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "header-row-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-1", + "type": "rectangle", + "x": 304, + "y": 192, + "width": 1112, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-checkbox", + "type": "rectangle", + "x": 320, + "y": 210, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-avatar", + "type": "rectangle", + "x": 360, + "y": 206, + "width": 28, + "height": 28, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 14 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-name", + "type": "text", + "x": 396, + "y": 204, + "width": 80, + "height": 14, + "text": "John Doe", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-email", + "type": "text", + "x": 396, + "y": 220, + "width": 130, + "height": 12, + "text": "john@example.com", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-badge", + "type": "rectangle", + "x": 580, + "y": 210, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-text", + "type": "text", + "x": 592, + "y": 213, + "width": 40, + "height": 14, + "text": "Admin", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-2fa-text", + "type": "text", + "x": 705, + "y": 213, + "width": 20, + "height": 14, + "text": "Y", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$success", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status-badge", + "type": "rectangle", + "x": 820, + "y": 210, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status-text", + "type": "text", + "x": 832, + "y": 213, + "width": 40, + "height": 14, + "text": "Active", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$success", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-created", + "type": "text", + "x": 980, + "y": 213, + "width": 50, + "height": 14, + "text": "Jan 15", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-1-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-actions", + "type": "rectangle", + "x": 1356, + "y": 210, + "width": 24, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-1-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-2", + "type": "rectangle", + "x": 304, + "y": 248, + "width": 1112, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-checkbox", + "type": "rectangle", + "x": 320, + "y": 266, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-avatar", + "type": "rectangle", + "x": 360, + "y": 262, + "width": 28, + "height": 28, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 14 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-name", + "type": "text", + "x": 396, + "y": 260, + "width": 90, + "height": 14, + "text": "Jane Smith", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-email", + "type": "text", + "x": 396, + "y": 276, + "width": 130, + "height": 12, + "text": "jane@example.com", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-badge", + "type": "rectangle", + "x": 580, + "y": 266, + "width": 50, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-text", + "type": "text", + "x": 592, + "y": 269, + "width": 30, + "height": 14, + "text": "User", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-2fa-text", + "type": "text", + "x": 705, + "y": 269, + "width": 20, + "height": 14, + "text": "-", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status-badge", + "type": "rectangle", + "x": 820, + "y": 266, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status-text", + "type": "text", + "x": 832, + "y": 269, + "width": 40, + "height": 14, + "text": "Active", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$success", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-created", + "type": "text", + "x": 980, + "y": 269, + "width": 50, + "height": 14, + "text": "Jan 12", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-2-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-actions", + "type": "rectangle", + "x": 1356, + "y": 266, + "width": 24, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-2-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-3", + "type": "rectangle", + "x": 304, + "y": 304, + "width": 1112, + "height": 56, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-checkbox", + "type": "rectangle", + "x": 320, + "y": 322, + "width": 20, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-avatar", + "type": "rectangle", + "x": 360, + "y": 318, + "width": 28, + "height": 28, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 14 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-name", + "type": "text", + "x": 396, + "y": 316, + "width": 90, + "height": 14, + "text": "Bob Wilson", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-email", + "type": "text", + "x": 396, + "y": 332, + "width": 130, + "height": 12, + "text": "bob@example.com", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-badge", + "type": "rectangle", + "x": 580, + "y": 322, + "width": 50, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-text", + "type": "text", + "x": 592, + "y": 325, + "width": 30, + "height": 14, + "text": "User", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-2fa-text", + "type": "text", + "x": 705, + "y": 325, + "width": 20, + "height": 14, + "text": "Y", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$success", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-status-badge", + "type": "rectangle", + "x": 820, + "y": 322, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 10 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-status-text", + "type": "text", + "x": 828, + "y": 325, + "width": 45, + "height": 14, + "text": "Banned", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-created", + "type": "text", + "x": 980, + "y": 325, + "width": 50, + "height": 14, + "text": "Jan 10", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "row-3-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-actions", + "type": "rectangle", + "x": 1356, + "y": 322, + "width": 24, + "height": 20, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "row-3-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "empty-rows-area", + "type": "rectangle", + "x": 304, + "y": 360, + "width": 1112, + "height": 460, + "strokeColor": "transparent", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-bar", + "type": "rectangle", + "x": 304, + "y": 820, + "width": 1112, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-info", + "type": "text", + "x": 328, + "y": 836, + "width": 150, + "height": 16, + "text": "Showing 1-10 of 1,234", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-prev", + "type": "rectangle", + "x": 1124, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-1", + "type": "rectangle", + "x": 1164, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-1-text", + "type": "text", + "x": 1176, + "y": 838, + "width": 10, + "height": 12, + "text": "1", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-2", + "type": "rectangle", + "x": 1204, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-2-text", + "type": "text", + "x": 1216, + "y": 838, + "width": 10, + "height": 12, + "text": "2", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-3", + "type": "rectangle", + "x": 1244, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-3-text", + "type": "text", + "x": 1256, + "y": 838, + "width": 10, + "height": 12, + "text": "3", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-ellipsis", + "type": "text", + "x": 1284, + "y": 838, + "width": 20, + "height": 12, + "text": "...", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-10", + "type": "rectangle", + "x": 1316, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-10-text", + "type": "text", + "x": 1324, + "y": 838, + "width": 16, + "height": 12, + "text": "124", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": ["table-group", "pagination-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-next", + "type": "rectangle", + "x": 1356, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["table-group", "pagination-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/medium-fidelity/settings-billing.excalidraw b/.context/turbostarter-framework-context/wireframes/medium-fidelity/settings-billing.excalidraw new file mode 100644 index 0000000..12ddce4 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/medium-fidelity/settings-billing.excalidraw @@ -0,0 +1,2351 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-text", + "type": "text", + "x": 40, + "y": 24, + "width": 80, + "height": 16, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-1", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-1-text", + "type": "text", + "x": 40, + "y": 112, + "width": 80, + "height": 16, + "text": "Dashboard", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-2", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-2-text", + "type": "text", + "x": 40, + "y": 172, + "width": 80, + "height": 16, + "text": "Browse MCPs", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-3", + "type": "rectangle", + "x": 20, + "y": 220, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-3-text", + "type": "text", + "x": 40, + "y": 232, + "width": 80, + "height": 16, + "text": "Bundles", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-active", + "type": "rectangle", + "x": 20, + "y": 280, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-text", + "type": "text", + "x": 40, + "y": 292, + "width": 80, + "height": 16, + "text": "Settings", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user-text", + "type": "text", + "x": 40, + "y": 860, + "width": 100, + "height": 16, + "text": "John Doe", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "rectangle", + "x": 304, + "y": 20, + "width": 120, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title-text", + "type": "text", + "x": 314, + "y": 24, + "width": 100, + "height": 18, + "text": "Settings", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-general", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-general-text", + "type": "text", + "x": 320, + "y": 96, + "width": 48, + "height": 16, + "text": "General", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-security", + "type": "rectangle", + "x": 404, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-security-text", + "type": "text", + "x": 416, + "y": 96, + "width": 56, + "height": 16, + "text": "Security", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-billing-active", + "type": "rectangle", + "x": 504, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-billing-text", + "type": "text", + "x": 524, + "y": 96, + "width": 40, + "height": 16, + "text": "Billing", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-card", + "type": "rectangle", + "x": 304, + "y": 140, + "width": 540, + "height": 200, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["plan-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-title", + "type": "rectangle", + "x": 324, + "y": 160, + "width": 120, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["plan-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-title-text", + "type": "text", + "x": 324, + "y": 160, + "width": 120, + "height": 20, + "text": "Current Plan", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["plan-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-name-badge", + "type": "rectangle", + "x": 324, + "y": 200, + "width": 60, + "height": 28, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["plan-section"], + "roundness": { "type": 3, "value": 14 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-name-badge-text", + "type": "text", + "x": 340, + "y": 206, + "width": 28, + "height": 16, + "text": "Pro", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["plan-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-price", + "type": "rectangle", + "x": 400, + "y": 200, + "width": 100, + "height": 28, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["plan-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-price-text", + "type": "text", + "x": 400, + "y": 206, + "width": 100, + "height": 16, + "text": "$29/month", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["plan-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-feature-1", + "type": "rectangle", + "x": 324, + "y": 244, + "width": 200, + "height": 14, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["plan-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-feature-1-text", + "type": "text", + "x": 324, + "y": 244, + "width": 200, + "height": 14, + "text": "Unlimited chats", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["plan-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-feature-2", + "type": "rectangle", + "x": 324, + "y": 264, + "width": 180, + "height": 14, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["plan-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-feature-2-text", + "type": "text", + "x": 324, + "y": 264, + "width": 180, + "height": 14, + "text": "Priority support", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["plan-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-feature-3", + "type": "rectangle", + "x": 324, + "y": 284, + "width": 160, + "height": 14, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["plan-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-feature-3-text", + "type": "text", + "x": 324, + "y": 284, + "width": 160, + "height": 14, + "text": "API access", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["plan-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "manage-subscription-button", + "type": "rectangle", + "x": 680, + "y": 180, + "width": 140, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["plan-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "manage-subscription-button-text", + "type": "text", + "x": 684, + "y": 192, + "width": 132, + "height": 16, + "text": "Manage subscription", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["plan-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-card", + "type": "rectangle", + "x": 864, + "y": 140, + "width": 280, + "height": 200, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["credits-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-title", + "type": "rectangle", + "x": 884, + "y": 160, + "width": 80, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["credits-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-title-text", + "type": "text", + "x": 884, + "y": 160, + "width": 80, + "height": 20, + "text": "Credits", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["credits-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-balance", + "type": "rectangle", + "x": 884, + "y": 200, + "width": 140, + "height": 32, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["credits-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-balance-text", + "type": "text", + "x": 884, + "y": 204, + "width": 140, + "height": 24, + "text": "2,450 remaining", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["credits-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-usage-bar-bg", + "type": "rectangle", + "x": 884, + "y": 252, + "width": 240, + "height": 12, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 40, + "groupIds": ["credits-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-usage-bar-fill", + "type": "rectangle", + "x": 884, + "y": 252, + "width": 144, + "height": 12, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["credits-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "buy-credits-button", + "type": "rectangle", + "x": 884, + "y": 284, + "width": 120, + "height": 40, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["credits-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "buy-credits-button-text", + "type": "text", + "x": 904, + "y": 296, + "width": 80, + "height": 16, + "text": "Buy credits", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["credits-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "payment-method-card", + "type": "rectangle", + "x": 304, + "y": 360, + "width": 540, + "height": 100, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["payment-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "payment-title", + "type": "rectangle", + "x": 324, + "y": 380, + "width": 140, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["payment-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "payment-title-text", + "type": "text", + "x": 324, + "y": 380, + "width": 140, + "height": 20, + "text": "Payment Method", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["payment-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-icon", + "type": "rectangle", + "x": 324, + "y": 416, + "width": 40, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["payment-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-last-digits", + "type": "rectangle", + "x": 380, + "y": 420, + "width": 100, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["payment-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-last-digits-text", + "type": "text", + "x": 380, + "y": 420, + "width": 100, + "height": 16, + "text": "•••• 4242", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["payment-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "update-payment-button", + "type": "rectangle", + "x": 720, + "y": 400, + "width": 100, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["payment-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "update-payment-button-text", + "type": "text", + "x": 744, + "y": 412, + "width": 52, + "height": 16, + "text": "Update", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["payment-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "billing-history-card", + "type": "rectangle", + "x": 304, + "y": 480, + "width": 840, + "height": 340, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "history-title", + "type": "rectangle", + "x": 324, + "y": 500, + "width": 140, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "history-title-text", + "type": "text", + "x": 324, + "y": 500, + "width": 140, + "height": 20, + "text": "Billing History", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-header", + "type": "rectangle", + "x": 324, + "y": 540, + "width": 800, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 40, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-date", + "type": "rectangle", + "x": 344, + "y": 552, + "width": 60, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-date-text", + "type": "text", + "x": 344, + "y": 552, + "width": 60, + "height": 16, + "text": "Date", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-amount", + "type": "rectangle", + "x": 544, + "y": 552, + "width": 80, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-amount-text", + "type": "text", + "x": 544, + "y": 552, + "width": 80, + "height": 16, + "text": "Amount", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-status", + "type": "rectangle", + "x": 744, + "y": 552, + "width": 60, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-status-text", + "type": "text", + "x": 744, + "y": 552, + "width": 60, + "height": 16, + "text": "Status", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invoice", + "type": "rectangle", + "x": 944, + "y": 552, + "width": 80, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invoice-text", + "type": "text", + "x": 944, + "y": 552, + "width": 80, + "height": 16, + "text": "Invoice", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1", + "type": "rectangle", + "x": 324, + "y": 580, + "width": 800, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-date", + "type": "rectangle", + "x": 344, + "y": 596, + "width": 100, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-date-text", + "type": "text", + "x": 344, + "y": 596, + "width": 100, + "height": 16, + "text": "Jan 1, 2024", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-amount", + "type": "rectangle", + "x": 544, + "y": 596, + "width": 60, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-amount-text", + "type": "text", + "x": 544, + "y": 596, + "width": 60, + "height": 16, + "text": "$29.00", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status", + "type": "rectangle", + "x": 744, + "y": 592, + "width": 60, + "height": 24, + "strokeColor": "$success", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status-text", + "type": "text", + "x": 758, + "y": 596, + "width": 32, + "height": 16, + "text": "Paid", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-invoice", + "type": "rectangle", + "x": 944, + "y": 596, + "width": 80, + "height": 16, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-invoice-text", + "type": "text", + "x": 948, + "y": 596, + "width": 72, + "height": 16, + "text": "Download", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2", + "type": "rectangle", + "x": 324, + "y": 628, + "width": 800, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-date", + "type": "rectangle", + "x": 344, + "y": 644, + "width": 100, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-date-text", + "type": "text", + "x": 344, + "y": 644, + "width": 100, + "height": 16, + "text": "Dec 1, 2023", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-amount", + "type": "rectangle", + "x": 544, + "y": 644, + "width": 60, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-amount-text", + "type": "text", + "x": 544, + "y": 644, + "width": 60, + "height": 16, + "text": "$29.00", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status", + "type": "rectangle", + "x": 744, + "y": 640, + "width": 60, + "height": 24, + "strokeColor": "$success", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status-text", + "type": "text", + "x": 758, + "y": 644, + "width": 32, + "height": 16, + "text": "Paid", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-invoice", + "type": "rectangle", + "x": 944, + "y": 644, + "width": 80, + "height": 16, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-invoice-text", + "type": "text", + "x": 948, + "y": 644, + "width": 72, + "height": 16, + "text": "Download", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3", + "type": "rectangle", + "x": 324, + "y": 676, + "width": 800, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-date", + "type": "rectangle", + "x": 344, + "y": 692, + "width": 100, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-date-text", + "type": "text", + "x": 344, + "y": 692, + "width": 100, + "height": 16, + "text": "Nov 1, 2023", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-amount", + "type": "rectangle", + "x": 544, + "y": 692, + "width": 60, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-amount-text", + "type": "text", + "x": 544, + "y": 692, + "width": 60, + "height": 16, + "text": "$29.00", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-status", + "type": "rectangle", + "x": 744, + "y": 688, + "width": 60, + "height": 24, + "strokeColor": "$success", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-status-text", + "type": "text", + "x": 758, + "y": 692, + "width": 32, + "height": 16, + "text": "Paid", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-invoice", + "type": "rectangle", + "x": 944, + "y": 692, + "width": 80, + "height": 16, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-invoice-text", + "type": "text", + "x": 948, + "y": 692, + "width": 72, + "height": 16, + "text": "Download", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4", + "type": "rectangle", + "x": 324, + "y": 724, + "width": 800, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-date", + "type": "rectangle", + "x": 344, + "y": 740, + "width": 100, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-date-text", + "type": "text", + "x": 344, + "y": 740, + "width": 100, + "height": 16, + "text": "Oct 1, 2023", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-amount", + "type": "rectangle", + "x": 544, + "y": 740, + "width": 60, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-amount-text", + "type": "text", + "x": 544, + "y": 740, + "width": 60, + "height": 16, + "text": "$29.00", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-status", + "type": "rectangle", + "x": 744, + "y": 736, + "width": 60, + "height": 24, + "strokeColor": "$success", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-status-text", + "type": "text", + "x": 758, + "y": 740, + "width": 32, + "height": 16, + "text": "Paid", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-invoice", + "type": "rectangle", + "x": 944, + "y": 740, + "width": 80, + "height": 16, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["history-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-invoice-text", + "type": "text", + "x": 948, + "y": 740, + "width": 72, + "height": 16, + "text": "Download", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["history-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/medium-fidelity/settings-general.excalidraw b/.context/turbostarter-framework-context/wireframes/medium-fidelity/settings-general.excalidraw new file mode 100644 index 0000000..9a8a673 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/medium-fidelity/settings-general.excalidraw @@ -0,0 +1,1336 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-text", + "type": "text", + "x": 40, + "y": 24, + "width": 80, + "height": 16, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-1", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-1-text", + "type": "text", + "x": 40, + "y": 112, + "width": 80, + "height": 16, + "text": "Dashboard", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-2", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-2-text", + "type": "text", + "x": 40, + "y": 172, + "width": 80, + "height": 16, + "text": "Browse MCPs", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-3", + "type": "rectangle", + "x": 20, + "y": 220, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-3-text", + "type": "text", + "x": 40, + "y": 232, + "width": 80, + "height": 16, + "text": "Bundles", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-active", + "type": "rectangle", + "x": 20, + "y": 280, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-text", + "type": "text", + "x": 40, + "y": 292, + "width": 80, + "height": 16, + "text": "Settings", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user-text", + "type": "text", + "x": 40, + "y": 860, + "width": 100, + "height": 16, + "text": "John Doe", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "rectangle", + "x": 304, + "y": 20, + "width": 120, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title-text", + "type": "text", + "x": 314, + "y": 24, + "width": 100, + "height": 18, + "text": "Settings", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-general-active", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-general-text", + "type": "text", + "x": 320, + "y": 96, + "width": 48, + "height": 16, + "text": "General", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-security", + "type": "rectangle", + "x": 404, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-security-text", + "type": "text", + "x": 416, + "y": 96, + "width": 56, + "height": 16, + "text": "Security", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-billing", + "type": "rectangle", + "x": 504, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-billing-text", + "type": "text", + "x": 524, + "y": 96, + "width": 40, + "height": 16, + "text": "Billing", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "profile-card", + "type": "rectangle", + "x": 304, + "y": 140, + "width": 700, + "height": 280, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "profile-card-title", + "type": "rectangle", + "x": 324, + "y": 160, + "width": 100, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "profile-card-title-text", + "type": "text", + "x": 324, + "y": 160, + "width": 100, + "height": 20, + "text": "Profile", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "avatar-circle", + "type": "ellipse", + "x": 324, + "y": 200, + "width": 80, + "height": 80, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "avatar-initials", + "type": "text", + "x": 344, + "y": 232, + "width": 40, + "height": 16, + "text": "JD", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["profile-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "avatar-change-button", + "type": "rectangle", + "x": 324, + "y": 300, + "width": 100, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "avatar-change-button-text", + "type": "text", + "x": 330, + "y": 308, + "width": 88, + "height": 16, + "text": "Change avatar", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-label", + "type": "rectangle", + "x": 440, + "y": 200, + "width": 60, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["profile-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-label-text", + "type": "text", + "x": 440, + "y": 200, + "width": 60, + "height": 16, + "text": "Full name", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-input", + "type": "rectangle", + "x": 440, + "y": 220, + "width": 540, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-input-text", + "type": "text", + "x": 456, + "y": 232, + "width": 80, + "height": 16, + "text": "John Doe", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-label", + "type": "rectangle", + "x": 440, + "y": 280, + "width": 60, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["profile-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-label-text", + "type": "text", + "x": 440, + "y": 280, + "width": 60, + "height": 16, + "text": "Email", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-input", + "type": "rectangle", + "x": 440, + "y": 300, + "width": 440, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-input-text", + "type": "text", + "x": 456, + "y": 312, + "width": 140, + "height": 16, + "text": "john@example.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "verified-badge", + "type": "rectangle", + "x": 900, + "y": 308, + "width": 80, + "height": 24, + "strokeColor": "$success", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "verified-badge-text", + "type": "text", + "x": 916, + "y": 312, + "width": 48, + "height": 16, + "text": "Verified", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "save-profile-button", + "type": "rectangle", + "x": 324, + "y": 360, + "width": 120, + "height": 40, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "save-profile-button-text", + "type": "text", + "x": 344, + "y": 372, + "width": 80, + "height": 16, + "text": "Save changes", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["profile-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-card", + "type": "rectangle", + "x": 304, + "y": 440, + "width": 700, + "height": 140, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["language-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-card-title", + "type": "rectangle", + "x": 324, + "y": 460, + "width": 100, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["language-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-card-title-text", + "type": "text", + "x": 324, + "y": 460, + "width": 100, + "height": 20, + "text": "Language", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["language-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-label", + "type": "rectangle", + "x": 324, + "y": 500, + "width": 120, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["language-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-label-text", + "type": "text", + "x": 324, + "y": 500, + "width": 120, + "height": 16, + "text": "Display language", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["language-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-dropdown", + "type": "rectangle", + "x": 324, + "y": 520, + "width": 300, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["language-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-dropdown-text", + "type": "text", + "x": 340, + "y": 532, + "width": 60, + "height": 16, + "text": "English", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["language-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "dropdown-chevron", + "type": "rectangle", + "x": 588, + "y": 532, + "width": 16, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["language-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "danger-zone-card", + "type": "rectangle", + "x": 304, + "y": 600, + "width": 700, + "height": 160, + "strokeColor": "$destructive", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["danger-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "danger-zone-title", + "type": "rectangle", + "x": 324, + "y": 620, + "width": 120, + "height": 20, + "strokeColor": "$destructive", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["danger-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "danger-zone-title-text", + "type": "text", + "x": 324, + "y": 620, + "width": 120, + "height": 20, + "text": "Danger Zone", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["danger-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "danger-zone-description", + "type": "rectangle", + "x": 324, + "y": 660, + "width": 400, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["danger-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "danger-zone-description-text", + "type": "text", + "x": 324, + "y": 660, + "width": 400, + "height": 16, + "text": "Delete your account permanently. This action cannot be undone.", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["danger-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "delete-account-button", + "type": "rectangle", + "x": 324, + "y": 700, + "width": 140, + "height": 40, + "strokeColor": "$destructive", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["danger-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "delete-account-button-text", + "type": "text", + "x": 340, + "y": 712, + "width": 108, + "height": 16, + "text": "Delete Account", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["danger-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/medium-fidelity/settings-security.excalidraw b/.context/turbostarter-framework-context/wireframes/medium-fidelity/settings-security.excalidraw new file mode 100644 index 0000000..5023e73 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/medium-fidelity/settings-security.excalidraw @@ -0,0 +1,1616 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-text", + "type": "text", + "x": 40, + "y": 24, + "width": 80, + "height": 16, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-1", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-1-text", + "type": "text", + "x": 40, + "y": 112, + "width": 80, + "height": 16, + "text": "Dashboard", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-2", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-2-text", + "type": "text", + "x": 40, + "y": 172, + "width": 80, + "height": 16, + "text": "Browse MCPs", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-3", + "type": "rectangle", + "x": 20, + "y": 220, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-3-text", + "type": "text", + "x": 40, + "y": 232, + "width": 80, + "height": 16, + "text": "Bundles", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-active", + "type": "rectangle", + "x": 20, + "y": 280, + "width": 240, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-text", + "type": "text", + "x": 40, + "y": 292, + "width": 80, + "height": 16, + "text": "Settings", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user-text", + "type": "text", + "x": 40, + "y": 860, + "width": 100, + "height": 16, + "text": "John Doe", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "rectangle", + "x": 304, + "y": 20, + "width": 120, + "height": 24, + "strokeColor": "$border", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title-text", + "type": "text", + "x": 314, + "y": 24, + "width": 100, + "height": 18, + "text": "Settings", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["header-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["content-group"], + "roundness": { "type": 3, "value": 0 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-general", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-general-text", + "type": "text", + "x": 320, + "y": 96, + "width": 48, + "height": 16, + "text": "General", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-security-active", + "type": "rectangle", + "x": 404, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-security-text", + "type": "text", + "x": 416, + "y": 96, + "width": 56, + "height": 16, + "text": "Security", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-billing", + "type": "rectangle", + "x": 504, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-billing-text", + "type": "text", + "x": 524, + "y": 96, + "width": 40, + "height": 16, + "text": "Billing", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["tabs-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-card", + "type": "rectangle", + "x": 304, + "y": 140, + "width": 700, + "height": 140, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["twofa-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-title", + "type": "rectangle", + "x": 324, + "y": 160, + "width": 200, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["twofa-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-title-text", + "type": "text", + "x": 324, + "y": 160, + "width": 200, + "height": 20, + "text": "Two-Factor Authentication", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["twofa-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-status-indicator", + "type": "rectangle", + "x": 324, + "y": 200, + "width": 80, + "height": 24, + "strokeColor": "$success", + "backgroundColor": "$success", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["twofa-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-status-text", + "type": "text", + "x": 340, + "y": 204, + "width": 48, + "height": 16, + "text": "Enabled", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["twofa-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-description", + "type": "rectangle", + "x": 324, + "y": 240, + "width": 500, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["twofa-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-description-text", + "type": "text", + "x": 324, + "y": 240, + "width": 500, + "height": 16, + "text": "Add an extra layer of security to your account with two-factor authentication.", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["twofa-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-toggle-button", + "type": "rectangle", + "x": 880, + "y": 180, + "width": 100, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["twofa-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-toggle-button-text", + "type": "text", + "x": 904, + "y": 192, + "width": 52, + "height": 16, + "text": "Disable", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["twofa-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkeys-card", + "type": "rectangle", + "x": 304, + "y": 300, + "width": 700, + "height": 220, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["passkeys-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkeys-title", + "type": "rectangle", + "x": 324, + "y": 320, + "width": 100, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["passkeys-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkeys-title-text", + "type": "text", + "x": 324, + "y": 320, + "width": 100, + "height": 20, + "text": "Passkeys", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["passkeys-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-item-1", + "type": "rectangle", + "x": 324, + "y": 360, + "width": 656, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["passkeys-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-1-name", + "type": "rectangle", + "x": 340, + "y": 372, + "width": 120, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["passkeys-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-1-name-text", + "type": "text", + "x": 340, + "y": 372, + "width": 120, + "height": 16, + "text": "MacBook Pro", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["passkeys-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-1-date", + "type": "rectangle", + "x": 500, + "y": 372, + "width": 120, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["passkeys-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-1-date-text", + "type": "text", + "x": 500, + "y": 372, + "width": 120, + "height": 16, + "text": "Created Jan 15", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["passkeys-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-1-delete", + "type": "rectangle", + "x": 920, + "y": 368, + "width": 48, + "height": 24, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["passkeys-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-1-delete-text", + "type": "text", + "x": 926, + "y": 372, + "width": 36, + "height": 16, + "text": "Delete", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["passkeys-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-item-2", + "type": "rectangle", + "x": 324, + "y": 420, + "width": 656, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["passkeys-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-2-name", + "type": "rectangle", + "x": 340, + "y": 432, + "width": 140, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["passkeys-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-2-name-text", + "type": "text", + "x": 340, + "y": 432, + "width": 140, + "height": 16, + "text": "iPhone", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["passkeys-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-2-date", + "type": "rectangle", + "x": 520, + "y": 432, + "width": 120, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["passkeys-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-2-date-text", + "type": "text", + "x": 520, + "y": 432, + "width": 120, + "height": 16, + "text": "Created Jan 20", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["passkeys-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-2-delete", + "type": "rectangle", + "x": 920, + "y": 428, + "width": 48, + "height": 24, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["passkeys-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-2-delete-text", + "type": "text", + "x": 926, + "y": 432, + "width": 36, + "height": 16, + "text": "Delete", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["passkeys-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "add-passkey-button", + "type": "rectangle", + "x": 324, + "y": 480, + "width": 120, + "height": 40, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["passkeys-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "add-passkey-button-text", + "type": "text", + "x": 340, + "y": 492, + "width": 88, + "height": 16, + "text": "Add passkey", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["passkeys-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sessions-card", + "type": "rectangle", + "x": 304, + "y": 540, + "width": 700, + "height": 220, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sessions-title", + "type": "rectangle", + "x": 324, + "y": 560, + "width": 140, + "height": 20, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sessions-title-text", + "type": "text", + "x": 324, + "y": 560, + "width": 140, + "height": 20, + "text": "Active Sessions", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sessions-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-item-1", + "type": "rectangle", + "x": 324, + "y": 600, + "width": 656, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-icon", + "type": "rectangle", + "x": 340, + "y": 612, + "width": 24, + "height": 24, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-name", + "type": "rectangle", + "x": 380, + "y": 608, + "width": 160, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-name-text", + "type": "text", + "x": 380, + "y": 608, + "width": 160, + "height": 16, + "text": "Chrome on macOS", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sessions-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-location", + "type": "rectangle", + "x": 380, + "y": 628, + "width": 120, + "height": 12, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-location-text", + "type": "text", + "x": 380, + "y": 628, + "width": 120, + "height": 12, + "text": "San Francisco, CA", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["sessions-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-current-badge", + "type": "rectangle", + "x": 900, + "y": 612, + "width": 68, + "height": 24, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 12 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-current-badge-text", + "type": "text", + "x": 912, + "y": 616, + "width": 44, + "height": 16, + "text": "Current", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sessions-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-item-2", + "type": "rectangle", + "x": 324, + "y": 660, + "width": 656, + "height": 48, + "strokeColor": "$border", + "backgroundColor": "$background", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 6 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-icon", + "type": "rectangle", + "x": 340, + "y": 672, + "width": 24, + "height": 24, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-name", + "type": "rectangle", + "x": 380, + "y": 668, + "width": 140, + "height": 16, + "strokeColor": "$foreground", + "backgroundColor": "$foreground", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-name-text", + "type": "text", + "x": 380, + "y": 668, + "width": 140, + "height": 16, + "text": "Safari on iPhone", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$background", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sessions-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-location", + "type": "rectangle", + "x": 380, + "y": 688, + "width": 100, + "height": 12, + "strokeColor": "$muted", + "backgroundColor": "$muted", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 2 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-location-text", + "type": "text", + "x": 380, + "y": 688, + "width": 100, + "height": 12, + "text": "San Francisco", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "$foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": ["sessions-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-revoke", + "type": "rectangle", + "x": 908, + "y": 672, + "width": 60, + "height": 24, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sessions-section"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-revoke-text", + "type": "text", + "x": 916, + "y": 676, + "width": 44, + "height": 16, + "text": "Revoke", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sessions-section"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/medium-fidelity/sidebar-admin.excalidraw b/.context/turbostarter-framework-context/wireframes/medium-fidelity/sidebar-admin.excalidraw new file mode 100644 index 0000000..6d9ff8a --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/medium-fidelity/sidebar-admin.excalidraw @@ -0,0 +1,726 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "sidebar-container", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-area", + "type": "rectangle", + "x": 24, + "y": 24, + "width": 160, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-icon", + "type": "rectangle", + "x": 36, + "y": 32, + "width": 24, + "height": 24, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 68, + "y": 34, + "width": 80, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 14 + }, + { + "id": "admin-badge", + "type": "rectangle", + "x": 196, + "y": 32, + "width": 60, + "height": 24, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "admin-badge-text", + "type": "text", + "x": 206, + "y": 36, + "width": 40, + "height": 16, + "strokeColor": "$primary", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Admin", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 10 + }, + { + "id": "section-divider-1", + "type": "line", + "x": 24, + "y": 88, + "width": 232, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [232, 0]] + }, + { + "id": "section-label-admin-text", + "type": "text", + "x": 24, + "y": 100, + "width": 50, + "height": 14, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "section-admin"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "ADMIN", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "nav-home-active", + "type": "rectangle", + "x": 24, + "y": 120, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-home-icon", + "type": "rectangle", + "x": 36, + "y": 130, + "width": 20, + "height": 20, + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-home-text", + "type": "text", + "x": 64, + "y": 130, + "width": 60, + "height": 20, + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Home", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-users", + "type": "rectangle", + "x": 24, + "y": 168, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-users-icon", + "type": "rectangle", + "x": 36, + "y": 178, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-users-text", + "type": "text", + "x": 64, + "y": 178, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Users", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-orgs", + "type": "rectangle", + "x": 24, + "y": 216, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-orgs-icon", + "type": "rectangle", + "x": 36, + "y": 226, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-orgs-text", + "type": "text", + "x": 64, + "y": 226, + "width": 100, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Organizations", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-customers", + "type": "rectangle", + "x": 24, + "y": 264, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-customers-icon", + "type": "rectangle", + "x": 36, + "y": 274, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-customers-text", + "type": "text", + "x": 64, + "y": 274, + "width": 80, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-admin"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Customers", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-2", + "type": "line", + "x": 24, + "y": 324, + "width": 232, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [232, 0]] + }, + { + "id": "nav-back", + "type": "rectangle", + "x": 24, + "y": 344, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-back"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-back-arrow", + "type": "text", + "x": 36, + "y": 354, + "width": 20, + "height": 20, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-back"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "<-", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-back-text", + "type": "text", + "x": 64, + "y": 354, + "width": 140, + "height": 20, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-back"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Back to Dashboard", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-footer", + "type": "line", + "x": 24, + "y": 812, + "width": 232, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [232, 0]] + }, + { + "id": "user-footer", + "type": "rectangle", + "x": 24, + "y": 824, + "width": 232, + "height": 64, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-avatar", + "type": "ellipse", + "x": 24, + "y": 836, + "width": 40, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-avatar-initials", + "type": "text", + "x": 32, + "y": 848, + "width": 24, + "height": 16, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "AU", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 10 + }, + { + "id": "user-name", + "type": "text", + "x": 76, + "y": 838, + "width": 100, + "height": 16, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Admin User", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 12 + }, + { + "id": "role-badge", + "type": "rectangle", + "x": 76, + "y": 858, + "width": 50, + "height": 18, + "strokeColor": "$destructive", + "backgroundColor": "$destructive", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["sidebar-group", "user-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "role-badge-text", + "type": "text", + "x": 84, + "y": 860, + "width": 34, + "height": 14, + "strokeColor": "$destructive", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "admin", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "dropdown-indicator", + "type": "rectangle", + "x": 224, + "y": 848, + "width": 16, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/medium-fidelity/sidebar-apps.excalidraw b/.context/turbostarter-framework-context/wireframes/medium-fidelity/sidebar-apps.excalidraw new file mode 100644 index 0000000..b6a62a7 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/medium-fidelity/sidebar-apps.excalidraw @@ -0,0 +1,1189 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "sidebar-container", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-area", + "type": "rectangle", + "x": 24, + "y": 24, + "width": 232, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-icon", + "type": "rectangle", + "x": 36, + "y": 32, + "width": 24, + "height": 24, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 68, + "y": 34, + "width": 80, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 14 + }, + { + "id": "section-divider-1", + "type": "line", + "x": 24, + "y": 88, + "width": 232, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [232, 0]] + }, + { + "id": "section-label-apps-text", + "type": "text", + "x": 24, + "y": 100, + "width": 40, + "height": 14, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "section-apps"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "APPS", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "nav-chat-active", + "type": "rectangle", + "x": 24, + "y": 120, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-chat-icon", + "type": "rectangle", + "x": 36, + "y": 130, + "width": 20, + "height": 20, + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-chat-text", + "type": "text", + "x": 64, + "y": 130, + "width": 60, + "height": 20, + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Chat", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-image", + "type": "rectangle", + "x": 24, + "y": 168, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-image-icon", + "type": "rectangle", + "x": 36, + "y": 178, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-image-text", + "type": "text", + "x": 64, + "y": 178, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Image", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-tts", + "type": "rectangle", + "x": 24, + "y": 216, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-tts-icon", + "type": "rectangle", + "x": 36, + "y": 226, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-tts-text", + "type": "text", + "x": 64, + "y": 226, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "TTS", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-pdf", + "type": "rectangle", + "x": 24, + "y": 264, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-pdf-icon", + "type": "rectangle", + "x": 36, + "y": 274, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-pdf-text", + "type": "text", + "x": 64, + "y": 274, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "PDF", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-agent", + "type": "rectangle", + "x": 24, + "y": 312, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-agent-icon", + "type": "rectangle", + "x": 36, + "y": 322, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-agent-text", + "type": "text", + "x": 64, + "y": 322, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-apps"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Agent", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-2", + "type": "line", + "x": 24, + "y": 372, + "width": 232, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [232, 0]] + }, + { + "id": "section-label-free-tools-text", + "type": "text", + "x": 24, + "y": 384, + "width": 80, + "height": 14, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "section-free-tools"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "FREE TOOLS", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "nav-envin", + "type": "rectangle", + "x": 24, + "y": 404, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-envin-icon", + "type": "rectangle", + "x": 36, + "y": 414, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-envin-text", + "type": "text", + "x": 64, + "y": 414, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Envin", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-ideas", + "type": "rectangle", + "x": 24, + "y": 452, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-ideas-icon", + "type": "rectangle", + "x": 36, + "y": 462, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-ideas-text", + "type": "text", + "x": 64, + "y": 462, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Ideas", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-extro", + "type": "rectangle", + "x": 24, + "y": 500, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-extro-icon", + "type": "rectangle", + "x": 36, + "y": 510, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-extro-text", + "type": "text", + "x": 64, + "y": 510, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Extro", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-emojai", + "type": "rectangle", + "x": 24, + "y": 548, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-emojai-icon", + "type": "rectangle", + "x": 36, + "y": 558, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-emojai-text", + "type": "text", + "x": 64, + "y": 558, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-free-tools"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Emojai", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-3", + "type": "line", + "x": 24, + "y": 608, + "width": 232, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [232, 0]] + }, + { + "id": "section-label-other-text", + "type": "text", + "x": 24, + "y": 620, + "width": 50, + "height": 14, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "section-other"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "OTHER", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "nav-home", + "type": "rectangle", + "x": 24, + "y": 640, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-other"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-home-icon", + "type": "rectangle", + "x": 36, + "y": 650, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-other"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-home-text", + "type": "text", + "x": 64, + "y": 650, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-other"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Home", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-docs", + "type": "rectangle", + "x": 24, + "y": 688, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-other"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-docs-icon", + "type": "rectangle", + "x": 36, + "y": 698, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-other"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-docs-text", + "type": "text", + "x": 64, + "y": 698, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-other"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Docs", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-blog", + "type": "rectangle", + "x": 24, + "y": 736, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-other"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-blog-icon", + "type": "rectangle", + "x": 36, + "y": 746, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-other"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-blog-text", + "type": "text", + "x": 64, + "y": 746, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-other"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Blog", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-footer", + "type": "line", + "x": 24, + "y": 812, + "width": 232, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [232, 0]] + }, + { + "id": "user-footer", + "type": "rectangle", + "x": 24, + "y": 824, + "width": 232, + "height": 64, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-avatar", + "type": "ellipse", + "x": 24, + "y": 836, + "width": 40, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-avatar-initials", + "type": "text", + "x": 32, + "y": 848, + "width": 24, + "height": 16, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "JD", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 10 + }, + { + "id": "user-name", + "type": "text", + "x": 76, + "y": 838, + "width": 120, + "height": 16, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "John Doe", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 12 + }, + { + "id": "user-email", + "type": "text", + "x": 76, + "y": 858, + "width": 140, + "height": 14, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "john@example.com", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 10 + }, + { + "id": "dropdown-indicator", + "type": "rectangle", + "x": 224, + "y": 848, + "width": 16, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/medium-fidelity/sidebar-dashboard.excalidraw b/.context/turbostarter-framework-context/wireframes/medium-fidelity/sidebar-dashboard.excalidraw new file mode 100644 index 0000000..3155446 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/medium-fidelity/sidebar-dashboard.excalidraw @@ -0,0 +1,788 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "sidebar-container", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "$border", + "backgroundColor": "$sidebar", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-area", + "type": "rectangle", + "x": 24, + "y": 24, + "width": 232, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$card", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-icon", + "type": "rectangle", + "x": 36, + "y": 32, + "width": 24, + "height": 24, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 68, + "y": 34, + "width": 80, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "logo-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 14 + }, + { + "id": "section-divider-1", + "type": "line", + "x": 24, + "y": 88, + "width": 232, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [232, 0]] + }, + { + "id": "section-label-platform-text", + "type": "text", + "x": 24, + "y": 100, + "width": 70, + "height": 14, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "section-platform"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "PLATFORM", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "nav-dashboard-active", + "type": "rectangle", + "x": 24, + "y": 120, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-platform"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-dashboard-icon", + "type": "rectangle", + "x": 36, + "y": 130, + "width": 20, + "height": 20, + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-platform"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-dashboard-text", + "type": "text", + "x": 64, + "y": 130, + "width": 80, + "height": 20, + "strokeColor": "$card", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-platform"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Dashboard", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-ai-tools", + "type": "rectangle", + "x": 24, + "y": 168, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-platform"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-ai-tools-icon", + "type": "rectangle", + "x": 36, + "y": 178, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-platform"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-ai-tools-text", + "type": "text", + "x": 64, + "y": 178, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-platform"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "AI Tools", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-2", + "type": "line", + "x": 24, + "y": 228, + "width": 232, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [232, 0]] + }, + { + "id": "section-label-manage-text", + "type": "text", + "x": 24, + "y": 240, + "width": 60, + "height": 14, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "section-manage"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "MANAGE", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "nav-settings", + "type": "rectangle", + "x": 24, + "y": 260, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-manage"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-settings-icon", + "type": "rectangle", + "x": 36, + "y": 270, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-manage"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-settings-text", + "type": "text", + "x": 64, + "y": 270, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-manage"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Settings", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-3", + "type": "line", + "x": 24, + "y": 320, + "width": 232, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [232, 0]] + }, + { + "id": "section-label-dev-text", + "type": "text", + "x": 24, + "y": 332, + "width": 30, + "height": 14, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "section-dev"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "DEV", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "nav-demos", + "type": "rectangle", + "x": 24, + "y": 352, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-dev"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-demos-icon", + "type": "rectangle", + "x": 36, + "y": 362, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-dev"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-demos-text", + "type": "text", + "x": 64, + "y": 362, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-dev"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Demos", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-bottom", + "type": "line", + "x": 24, + "y": 700, + "width": 232, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [232, 0]] + }, + { + "id": "nav-support", + "type": "rectangle", + "x": 24, + "y": 712, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-bottom"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-support-icon", + "type": "rectangle", + "x": 36, + "y": 722, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-bottom"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-support-text", + "type": "text", + "x": 64, + "y": 722, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-bottom"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Support", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-feedback", + "type": "rectangle", + "x": 24, + "y": 760, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-bottom"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-feedback-icon", + "type": "rectangle", + "x": 36, + "y": 770, + "width": 20, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-bottom"], + "roundness": { "type": 3, "value": 4 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-feedback-text", + "type": "text", + "x": 64, + "y": 770, + "width": 60, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "nav-bottom"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Feedback", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-footer", + "type": "line", + "x": 24, + "y": 812, + "width": 232, + "height": 0, + "strokeColor": "$border", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [[0, 0], [232, 0]] + }, + { + "id": "user-footer", + "type": "rectangle", + "x": 24, + "y": 824, + "width": 232, + "height": 64, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": { "type": 3, "value": 8 }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-avatar", + "type": "ellipse", + "x": 24, + "y": 836, + "width": 40, + "height": 40, + "strokeColor": "$border", + "backgroundColor": "$secondary", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-avatar-initials", + "type": "text", + "x": 32, + "y": 848, + "width": 24, + "height": 16, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "JD", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 10 + }, + { + "id": "user-name", + "type": "text", + "x": 76, + "y": 846, + "width": 120, + "height": 20, + "strokeColor": "$sidebar-foreground", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "John Doe", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "dropdown-indicator", + "type": "rectangle", + "x": 224, + "y": 848, + "width": 16, + "height": 16, + "strokeColor": "$muted", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": ["sidebar-group", "user-group"], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} diff --git a/.context/turbostarter-framework-context/wireframes/themed/high/auth-forgot-password.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/high/auth-forgot-password.excalidraw new file mode 100644 index 0000000..9d67d48 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/high/auth-forgot-password.excalidraw @@ -0,0 +1,968 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "left-column", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "left-column-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-column", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-column-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-gradient-overlay", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": [ + "right-column-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-pattern-circle-1", + "type": "ellipse", + "x": 800, + "y": 100, + "width": 200, + "height": 200, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 15, + "groupIds": [ + "right-column-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-pattern-circle-2", + "type": "ellipse", + "x": 1200, + "y": 600, + "width": 300, + "height": 300, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 10, + "groupIds": [ + "right-column-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-icon", + "type": "rectangle", + "x": 320, + "y": 200, + "width": 40, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "logo-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-icon-inner", + "type": "rectangle", + "x": 330, + "y": 210, + "width": 20, + "height": 20, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "logo-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 368, + "y": 208, + "width": 80, + "height": 24, + "text": "MCPGet", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "logo-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "lock-icon-bg", + "type": "ellipse", + "x": 336, + "y": 290, + "width": 48, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "icon-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "lock-icon-shackle", + "type": "rectangle", + "x": 350, + "y": 298, + "width": 20, + "height": 14, + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "icon-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "lock-icon-body", + "type": "rectangle", + "x": 346, + "y": 310, + "width": 28, + "height": 20, + "strokeColor": "#737373", + "backgroundColor": "#737373", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "icon-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "lock-icon-keyhole", + "type": "ellipse", + "x": 356, + "y": 316, + "width": 8, + "height": 8, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "icon-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "forgot-password-title-text", + "type": "text", + "x": 160, + "y": 370, + "width": 400, + "height": 32, + "text": "Forgot your password?", + "fontSize": 24, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "title-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "forgot-password-description-text", + "type": "text", + "x": 160, + "y": 410, + "width": 400, + "height": 40, + "text": "No worries! Enter your email address and we'll send you a link to reset your password.", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "title-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-label", + "type": "text", + "x": 160, + "y": 478, + "width": 100, + "height": 16, + "text": "Email address", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "email-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-input", + "type": "rectangle", + "x": 160, + "y": 502, + "width": 400, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "email-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-icon", + "type": "rectangle", + "x": 172, + "y": 516, + "width": 16, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "email-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-placeholder", + "type": "text", + "x": 200, + "y": 514, + "width": 200, + "height": 20, + "text": "you@example.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "email-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "send-reset-button", + "type": "rectangle", + "x": 160, + "y": 570, + "width": 400, + "height": 44, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "button-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "send-reset-button-text", + "type": "text", + "x": 160, + "y": 582, + "width": 400, + "height": 20, + "text": "Send reset link", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "button-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "back-arrow-line", + "type": "rectangle", + "x": 320, + "y": 658, + "width": 16, + "height": 2, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "back-link-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "back-arrow-head-top", + "type": "rectangle", + "x": 320, + "y": 654, + "width": 6, + "height": 2, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "back-link-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "back-arrow-head-bottom", + "type": "rectangle", + "x": 320, + "y": 662, + "width": 6, + "height": 2, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "back-link-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "back-to-login-link-text", + "type": "text", + "x": 344, + "y": 650, + "width": 100, + "height": 20, + "text": "Back to sign in", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "back-link-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "back-to-login-underline", + "type": "rectangle", + "x": 344, + "y": 668, + "width": 92, + "height": 1, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 40, + "groupIds": [ + "back-link-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "success-state-container", + "type": "rectangle", + "x": 160, + "y": 720, + "width": 400, + "height": 80, + "strokeColor": "#22c55e", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 10, + "groupIds": [ + "success-state-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "success-state-border", + "type": "rectangle", + "x": 160, + "y": 720, + "width": 400, + "height": 80, + "strokeColor": "#22c55e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "success-state-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "success-icon", + "type": "ellipse", + "x": 180, + "y": 740, + "width": 40, + "height": 40, + "strokeColor": "#22c55e", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 20, + "groupIds": [ + "success-state-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "success-check", + "type": "rectangle", + "x": 192, + "y": 758, + "width": 16, + "height": 2, + "strokeColor": "#22c55e", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "success-state-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "success-title", + "type": "text", + "x": 236, + "y": 738, + "width": 200, + "height": 20, + "text": "Check your email", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#22c55e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "success-state-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "success-description", + "type": "text", + "x": 236, + "y": 762, + "width": 300, + "height": 20, + "text": "We've sent a password reset link to your email", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "success-state-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-container", + "type": "rectangle", + "x": 880, + "y": 350, + "width": 400, + "height": 200, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-branding-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo-bg", + "type": "rectangle", + "x": 1020, + "y": 380, + "width": 120, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-branding-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo-icon", + "type": "rectangle", + "x": 1036, + "y": 394, + "width": 20, + "height": 20, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-branding-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo-text", + "type": "text", + "x": 1064, + "y": 394, + "width": 60, + "height": 20, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-branding-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-tagline", + "type": "text", + "x": 880, + "y": 452, + "width": 400, + "height": 24, + "text": "Secure access recovery", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-branding-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-description", + "type": "text", + "x": 920, + "y": 484, + "width": 320, + "height": 40, + "text": "Get back into your account quickly and securely with our password recovery system", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-branding-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/high/auth-join-org.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/high/auth-join-org.excalidraw new file mode 100644 index 0000000..db8d920 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/high/auth-join-org.excalidraw @@ -0,0 +1,929 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "left-column", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "left-column-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-column", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-column-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-gradient-overlay", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": [ + "right-column-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-pattern-circle-1", + "type": "ellipse", + "x": 800, + "y": 100, + "width": 200, + "height": 200, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 15, + "groupIds": [ + "right-column-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-pattern-circle-2", + "type": "ellipse", + "x": 1200, + "y": 600, + "width": 300, + "height": 300, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 10, + "groupIds": [ + "right-column-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-icon", + "type": "rectangle", + "x": 320, + "y": 120, + "width": 40, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "logo-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-icon-inner", + "type": "rectangle", + "x": 330, + "y": 130, + "width": 20, + "height": 20, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "logo-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 368, + "y": 128, + "width": 80, + "height": 24, + "text": "MCPGet", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "logo-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "join-org-title-text", + "type": "text", + "x": 160, + "y": 200, + "width": 400, + "height": 32, + "text": "Join Organization", + "fontSize": 24, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "title-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "join-org-subtitle-text", + "type": "text", + "x": 160, + "y": 240, + "width": 400, + "height": 20, + "text": "You've been invited to join an organization", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "title-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "invitation-card-shadow", + "type": "rectangle", + "x": 163, + "y": 303, + "width": 394, + "height": 264, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 5, + "groupIds": [ + "invitation-card-group" + ], + "roundness": { + "type": 3, + "value": 16 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "invitation-card", + "type": "rectangle", + "x": 160, + "y": 300, + "width": 400, + "height": 270, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "invitation-card-group" + ], + "roundness": { + "type": 3, + "value": 16 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "org-logo-bg", + "type": "rectangle", + "x": 328, + "y": 332, + "width": 64, + "height": 64, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "invitation-card-group" + ], + "roundness": { + "type": 3, + "value": 16 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "org-logo-text", + "type": "text", + "x": 328, + "y": 352, + "width": 64, + "height": 24, + "text": "AC", + "fontSize": 22, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "invitation-card-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "org-name-text", + "type": "text", + "x": 160, + "y": 420, + "width": 400, + "height": 28, + "text": "Acme Corporation", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "invitation-card-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "inviter-name-text", + "type": "text", + "x": 160, + "y": 456, + "width": 400, + "height": 16, + "text": "Invited by John Doe (john@acmecorp.com)", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "invitation-card-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "role-badge", + "type": "rectangle", + "x": 310, + "y": 492, + "width": 100, + "height": 28, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "role-badge-group" + ], + "roundness": { + "type": 3, + "value": 14 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "role-icon", + "type": "ellipse", + "x": 322, + "y": 500, + "width": 12, + "height": 12, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 40, + "groupIds": [ + "role-badge-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "role-badge-text", + "type": "text", + "x": 340, + "y": 498, + "width": 60, + "height": 20, + "text": "Member", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "role-badge-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "invitation-expires-text", + "type": "text", + "x": 160, + "y": 536, + "width": 400, + "height": 16, + "text": "This invitation expires in 7 days", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "invitation-card-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "accept-button", + "type": "rectangle", + "x": 160, + "y": 600, + "width": 400, + "height": 44, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "button-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "accept-button-icon", + "type": "ellipse", + "x": 292, + "y": 614, + "width": 16, + "height": 16, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "button-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "accept-button-text", + "type": "text", + "x": 316, + "y": 612, + "width": 150, + "height": 20, + "text": "Accept invitation", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "button-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "decline-button", + "type": "rectangle", + "x": 160, + "y": 660, + "width": 400, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "decline-button-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "decline-button-text", + "type": "text", + "x": 160, + "y": 672, + "width": 400, + "height": 20, + "text": "Decline", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "decline-button-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "wrong-account-text", + "type": "text", + "x": 248, + "y": 730, + "width": 120, + "height": 16, + "text": "Wrong account?", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "links-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sign-out-link-text", + "type": "text", + "x": 374, + "y": 730, + "width": 100, + "height": 16, + "text": "Sign out", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "links-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sign-out-link-underline", + "type": "rectangle", + "x": 374, + "y": 746, + "width": 52, + "height": 1, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 40, + "groupIds": [ + "links-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logged-in-as-text", + "type": "text", + "x": 160, + "y": 770, + "width": 400, + "height": 14, + "text": "Logged in as user@example.com", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "links-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-container", + "type": "rectangle", + "x": 880, + "y": 350, + "width": 400, + "height": 200, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-branding-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo-bg", + "type": "rectangle", + "x": 1020, + "y": 380, + "width": 120, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-branding-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo-icon", + "type": "rectangle", + "x": 1036, + "y": 394, + "width": 20, + "height": 20, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-branding-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo-text", + "type": "text", + "x": 1064, + "y": 394, + "width": 60, + "height": 20, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-branding-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-tagline", + "type": "text", + "x": 880, + "y": 452, + "width": 400, + "height": 24, + "text": "Collaborate with your team", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-branding-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-description", + "type": "text", + "x": 920, + "y": 484, + "width": 320, + "height": 40, + "text": "Share MCP configurations across your organization and work together seamlessly", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-branding-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/high/auth-login.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/high/auth-login.excalidraw new file mode 100644 index 0000000..de87134 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/high/auth-login.excalidraw @@ -0,0 +1,1199 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "left-column", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "left-column-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-column", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-column-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-gradient-overlay", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": [ + "right-column-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-pattern-circle-1", + "type": "ellipse", + "x": 800, + "y": 100, + "width": 200, + "height": 200, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 15, + "groupIds": [ + "right-column-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-pattern-circle-2", + "type": "ellipse", + "x": 1200, + "y": 600, + "width": 300, + "height": 300, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 10, + "groupIds": [ + "right-column-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "form-container", + "type": "rectangle", + "x": 160, + "y": 150, + "width": 400, + "height": 600, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-container-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-icon", + "type": "rectangle", + "x": 320, + "y": 150, + "width": 40, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "logo-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-icon-inner", + "type": "rectangle", + "x": 330, + "y": 160, + "width": 20, + "height": 20, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "logo-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 368, + "y": 158, + "width": 80, + "height": 24, + "text": "MCPGet", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "logo-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-title-text", + "type": "text", + "x": 160, + "y": 234, + "width": 400, + "height": 32, + "text": "Welcome back", + "fontSize": 24, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "title-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-subtitle-text", + "type": "text", + "x": 160, + "y": 274, + "width": 400, + "height": 20, + "text": "Sign in to your account to continue", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "title-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-label", + "type": "text", + "x": 160, + "y": 326, + "width": 100, + "height": 16, + "text": "Email address", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "email-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-input", + "type": "rectangle", + "x": 160, + "y": 350, + "width": 400, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "email-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-icon", + "type": "rectangle", + "x": 172, + "y": 364, + "width": 16, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "email-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-placeholder", + "type": "text", + "x": 200, + "y": 362, + "width": 200, + "height": 20, + "text": "you@example.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "email-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-label", + "type": "text", + "x": 160, + "y": 410, + "width": 80, + "height": 16, + "text": "Password", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "password-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-input", + "type": "rectangle", + "x": 160, + "y": 434, + "width": 400, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "password-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-lock-icon", + "type": "rectangle", + "x": 172, + "y": 448, + "width": 16, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "password-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-placeholder", + "type": "text", + "x": 200, + "y": 446, + "width": 200, + "height": 20, + "text": "Enter your password", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "password-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-eye-icon-outer", + "type": "ellipse", + "x": 528, + "y": 448, + "width": 20, + "height": 14, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "password-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-eye-icon-inner", + "type": "ellipse", + "x": 534, + "y": 451, + "width": 8, + "height": 8, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "password-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "forgot-password-link", + "type": "text", + "x": 440, + "y": 494, + "width": 120, + "height": 16, + "text": "Forgot password?", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "forgot-password-underline", + "type": "rectangle", + "x": 450, + "y": 510, + "width": 108, + "height": 1, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 40, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "login-button", + "type": "rectangle", + "x": 160, + "y": 534, + "width": 400, + "height": 44, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "button-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "login-button-text", + "type": "text", + "x": 160, + "y": 546, + "width": 400, + "height": 20, + "text": "Sign in", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "button-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-line-left", + "type": "rectangle", + "x": 160, + "y": 610, + "width": 170, + "height": 1, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "divider-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-text", + "type": "text", + "x": 340, + "y": 602, + "width": 40, + "height": 16, + "text": "or", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "divider-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-line-right", + "type": "rectangle", + "x": 390, + "y": 610, + "width": 170, + "height": 1, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "divider-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-google", + "type": "rectangle", + "x": 160, + "y": 642, + "width": 125, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-google-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-google-icon", + "type": "ellipse", + "x": 194, + "y": 654, + "width": 20, + "height": 20, + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-google-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-google-text", + "type": "text", + "x": 220, + "y": 654, + "width": 50, + "height": 20, + "text": "Google", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-google-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-github", + "type": "rectangle", + "x": 297, + "y": 642, + "width": 125, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-github-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-github-icon", + "type": "ellipse", + "x": 331, + "y": 654, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-github-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-github-text", + "type": "text", + "x": 357, + "y": 654, + "width": 50, + "height": 20, + "text": "GitHub", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-github-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-apple", + "type": "rectangle", + "x": 434, + "y": 642, + "width": 126, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-apple-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-apple-icon", + "type": "ellipse", + "x": 468, + "y": 654, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-apple-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-apple-text", + "type": "text", + "x": 494, + "y": 654, + "width": 40, + "height": 20, + "text": "Apple", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-apple-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "signup-text-prefix", + "type": "text", + "x": 248, + "y": 718, + "width": 160, + "height": 16, + "text": "Don't have an account?", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "signup-link-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "signup-link-text", + "type": "text", + "x": 414, + "y": 718, + "width": 60, + "height": 16, + "text": "Sign up", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "signup-link-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "signup-link-underline", + "type": "rectangle", + "x": 414, + "y": 734, + "width": 52, + "height": 1, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 40, + "groupIds": [ + "signup-link-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-container", + "type": "rectangle", + "x": 880, + "y": 350, + "width": 400, + "height": 200, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-branding-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo-bg", + "type": "rectangle", + "x": 1020, + "y": 380, + "width": 120, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-branding-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo-icon", + "type": "rectangle", + "x": 1036, + "y": 394, + "width": 20, + "height": 20, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-branding-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo-text", + "type": "text", + "x": 1064, + "y": 394, + "width": 60, + "height": 20, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-branding-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-tagline", + "type": "text", + "x": 880, + "y": 452, + "width": 400, + "height": 24, + "text": "Discover and install MCPs with ease", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-branding-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-description", + "type": "text", + "x": 920, + "y": 484, + "width": 320, + "height": 40, + "text": "Your one-stop solution for managing Model Context Protocol configurations", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-branding-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/high/auth-register.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/high/auth-register.excalidraw new file mode 100644 index 0000000..1387be3 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/high/auth-register.excalidraw @@ -0,0 +1,1609 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "left-column", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "left-column-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-column", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-column-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-gradient-overlay", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": [ + "right-column-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-pattern-circle-1", + "type": "ellipse", + "x": 800, + "y": 100, + "width": 200, + "height": 200, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 15, + "groupIds": [ + "right-column-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-pattern-circle-2", + "type": "ellipse", + "x": 1200, + "y": 600, + "width": 300, + "height": 300, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 10, + "groupIds": [ + "right-column-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-icon", + "type": "rectangle", + "x": 320, + "y": 60, + "width": 40, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "logo-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-icon-inner", + "type": "rectangle", + "x": 330, + "y": 70, + "width": 20, + "height": 20, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "logo-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 368, + "y": 68, + "width": 80, + "height": 24, + "text": "MCPGet", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "logo-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "create-account-title-text", + "type": "text", + "x": 160, + "y": 130, + "width": 400, + "height": 32, + "text": "Create your account", + "fontSize": 24, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "title-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "create-account-subtitle-text", + "type": "text", + "x": 160, + "y": 170, + "width": 400, + "height": 20, + "text": "Start your journey with MCPGet today", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "title-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-label", + "type": "text", + "x": 160, + "y": 214, + "width": 80, + "height": 16, + "text": "Full name", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "name-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-input", + "type": "rectangle", + "x": 160, + "y": 238, + "width": 400, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "name-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-user-icon", + "type": "ellipse", + "x": 172, + "y": 252, + "width": 16, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "name-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-placeholder", + "type": "text", + "x": 200, + "y": 250, + "width": 200, + "height": 20, + "text": "John Doe", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "name-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-label", + "type": "text", + "x": 160, + "y": 298, + "width": 100, + "height": 16, + "text": "Email address", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "email-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-input", + "type": "rectangle", + "x": 160, + "y": 322, + "width": 400, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "email-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-icon", + "type": "rectangle", + "x": 172, + "y": 336, + "width": 16, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "email-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-placeholder", + "type": "text", + "x": 200, + "y": 334, + "width": 200, + "height": 20, + "text": "you@example.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "email-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-label", + "type": "text", + "x": 160, + "y": 382, + "width": 80, + "height": 16, + "text": "Password", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "password-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-input", + "type": "rectangle", + "x": 160, + "y": 406, + "width": 400, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "password-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-lock-icon", + "type": "rectangle", + "x": 172, + "y": 420, + "width": 16, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "password-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-placeholder", + "type": "text", + "x": 200, + "y": 418, + "width": 200, + "height": 20, + "text": "Create a strong password", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "password-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-eye-icon-outer", + "type": "ellipse", + "x": 528, + "y": 421, + "width": 20, + "height": 14, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "password-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-eye-icon-inner", + "type": "ellipse", + "x": 534, + "y": 424, + "width": 8, + "height": 8, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "password-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-strength-bg", + "type": "rectangle", + "x": 160, + "y": 458, + "width": 400, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "password-strength-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-strength-fill", + "type": "rectangle", + "x": 160, + "y": 458, + "width": 200, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "password-strength-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-strength-text", + "type": "text", + "x": 160, + "y": 468, + "width": 120, + "height": 14, + "text": "Password strength: Good", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#22c55e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "password-strength-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "confirm-password-label", + "type": "text", + "x": 160, + "y": 494, + "width": 120, + "height": 16, + "text": "Confirm password", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "confirm-password-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "confirm-password-input", + "type": "rectangle", + "x": 160, + "y": 518, + "width": 400, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "confirm-password-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "confirm-password-lock-icon", + "type": "rectangle", + "x": 172, + "y": 532, + "width": 16, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "confirm-password-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "confirm-password-placeholder", + "type": "text", + "x": 200, + "y": 530, + "width": 200, + "height": 20, + "text": "Confirm your password", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "confirm-password-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "confirm-password-check-icon", + "type": "ellipse", + "x": 528, + "y": 532, + "width": 16, + "height": 16, + "strokeColor": "#22c55e", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "confirm-password-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "terms-checkbox", + "type": "rectangle", + "x": 160, + "y": 586, + "width": 18, + "height": 18, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "terms-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "terms-text", + "type": "text", + "x": 186, + "y": 585, + "width": 130, + "height": 20, + "text": "I agree to the", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "terms-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "terms-link", + "type": "text", + "x": 282, + "y": 585, + "width": 100, + "height": 20, + "text": "Terms of Service", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "terms-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "terms-link-underline", + "type": "rectangle", + "x": 282, + "y": 603, + "width": 95, + "height": 1, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 40, + "groupIds": [ + "terms-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "terms-and-text", + "type": "text", + "x": 382, + "y": 585, + "width": 30, + "height": 20, + "text": "and", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "terms-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "privacy-link", + "type": "text", + "x": 416, + "y": 585, + "width": 90, + "height": 20, + "text": "Privacy Policy", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "terms-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "privacy-link-underline", + "type": "rectangle", + "x": 416, + "y": 603, + "width": 82, + "height": 1, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 40, + "groupIds": [ + "terms-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "create-account-button", + "type": "rectangle", + "x": 160, + "y": 630, + "width": 400, + "height": 44, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "button-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "create-account-button-text", + "type": "text", + "x": 160, + "y": 642, + "width": 400, + "height": 20, + "text": "Create account", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "button-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-line-left", + "type": "rectangle", + "x": 160, + "y": 706, + "width": 170, + "height": 1, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "divider-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-text", + "type": "text", + "x": 340, + "y": 698, + "width": 40, + "height": 16, + "text": "or", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "divider-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-line-right", + "type": "rectangle", + "x": 390, + "y": 706, + "width": 170, + "height": 1, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "divider-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-google", + "type": "rectangle", + "x": 160, + "y": 734, + "width": 125, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-google-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-google-icon", + "type": "ellipse", + "x": 194, + "y": 746, + "width": 20, + "height": 20, + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-google-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-google-text", + "type": "text", + "x": 220, + "y": 746, + "width": 50, + "height": 20, + "text": "Google", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-google-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-github", + "type": "rectangle", + "x": 297, + "y": 734, + "width": 125, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-github-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-github-icon", + "type": "ellipse", + "x": 331, + "y": 746, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-github-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-github-text", + "type": "text", + "x": 357, + "y": 746, + "width": 50, + "height": 20, + "text": "GitHub", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-github-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-apple", + "type": "rectangle", + "x": 434, + "y": 734, + "width": 126, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-apple-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-apple-icon", + "type": "ellipse", + "x": 468, + "y": 746, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-apple-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-apple-text", + "type": "text", + "x": 494, + "y": 746, + "width": 40, + "height": 20, + "text": "Apple", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-apple-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "login-text-prefix", + "type": "text", + "x": 232, + "y": 810, + "width": 170, + "height": 16, + "text": "Already have an account?", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "login-link-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "login-link-text", + "type": "text", + "x": 410, + "y": 810, + "width": 50, + "height": 16, + "text": "Sign in", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "login-link-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "login-link-underline", + "type": "rectangle", + "x": 410, + "y": 826, + "width": 44, + "height": 1, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 40, + "groupIds": [ + "login-link-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-container", + "type": "rectangle", + "x": 880, + "y": 350, + "width": 400, + "height": 200, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-branding-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo-bg", + "type": "rectangle", + "x": 1020, + "y": 380, + "width": 120, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-branding-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo-icon", + "type": "rectangle", + "x": 1036, + "y": 394, + "width": 20, + "height": 20, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-branding-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo-text", + "type": "text", + "x": 1064, + "y": 394, + "width": 60, + "height": 20, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-branding-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-tagline", + "type": "text", + "x": 880, + "y": 452, + "width": 400, + "height": 24, + "text": "Join thousands of developers", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-branding-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-description", + "type": "text", + "x": 920, + "y": 484, + "width": 320, + "height": 40, + "text": "Manage your MCP configurations effortlessly with our powerful toolkit", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-branding-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/high/dashboard-admin.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/high/dashboard-admin.excalidraw new file mode 100644 index 0000000..27ebfc5 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/high/dashboard-admin.excalidraw @@ -0,0 +1,2874 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 24, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-text", + "type": "text", + "x": 36, + "y": 24, + "width": 96, + "height": 18, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-admin-badge", + "type": "rectangle", + "x": 160, + "y": 22, + "width": 52, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-admin-badge-text", + "type": "text", + "x": 168, + "y": 26, + "width": 36, + "height": 12, + "text": "Admin", + "fontSize": 9, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-home", + "type": "rectangle", + "x": 24, + "y": 100, + "width": 232, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-home-icon", + "type": "rectangle", + "x": 36, + "y": 110, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-home-icon-house", + "type": "rectangle", + "x": 40, + "y": 118, + "width": 12, + "height": 8, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-home-icon-roof", + "type": "rectangle", + "x": 38, + "y": 114, + "width": 16, + "height": 6, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-home-text", + "type": "text", + "x": 68, + "y": 112, + "width": 50, + "height": 16, + "text": "Home", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users", + "type": "rectangle", + "x": 24, + "y": 156, + "width": 232, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-icon", + "type": "rectangle", + "x": 36, + "y": 166, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-icon-head", + "type": "rectangle", + "x": 42, + "y": 168, + "width": 8, + "height": 8, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-icon-body", + "type": "rectangle", + "x": 40, + "y": 178, + "width": 12, + "height": 6, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 3 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-text", + "type": "text", + "x": 68, + "y": 168, + "width": 50, + "height": 16, + "text": "Users", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-orgs", + "type": "rectangle", + "x": 24, + "y": 212, + "width": 232, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-orgs-icon", + "type": "rectangle", + "x": 36, + "y": 222, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-orgs-text", + "type": "text", + "x": 68, + "y": 224, + "width": 100, + "height": 16, + "text": "Organizations", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-customers", + "type": "rectangle", + "x": 24, + "y": 268, + "width": 232, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-customers-icon", + "type": "rectangle", + "x": 36, + "y": 278, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-customers-text", + "type": "text", + "x": 68, + "y": 280, + "width": 80, + "height": 16, + "text": "Customers", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 24, + "y": 852, + "width": 232, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-avatar", + "type": "rectangle", + "x": 32, + "y": 858, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-avatar-initials", + "type": "text", + "x": 37, + "y": 861, + "width": 10, + "height": 14, + "text": "A", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-name", + "type": "text", + "x": 60, + "y": 860, + "width": 80, + "height": 14, + "text": "Admin", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "text", + "x": 304, + "y": 20, + "width": 160, + "height": 24, + "text": "Admin Dashboard", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-system-status", + "type": "rectangle", + "x": 1140, + "y": 18, + "width": 180, + "height": 28, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 14 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-system-status-dot", + "type": "rectangle", + "x": 1152, + "y": 26, + "width": 12, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-system-status-text", + "type": "text", + "x": 1172, + "y": 25, + "width": 140, + "height": 14, + "text": "All systems operational", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-user-avatar", + "type": "rectangle", + "x": 1388, + "y": 16, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 16 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-shadow", + "type": "rectangle", + "x": 306, + "y": 90, + "width": 260, + "height": 108, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 260, + "height": 108, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-label", + "type": "text", + "x": 328, + "y": 112, + "width": 80, + "height": 16, + "text": "Total Users", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-value", + "type": "text", + "x": 328, + "y": 140, + "width": 100, + "height": 36, + "text": "1,234", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-trend", + "type": "text", + "x": 488, + "y": 148, + "width": 60, + "height": 14, + "text": "+18%", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "#22c55e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-trend-arrow", + "type": "rectangle", + "x": 534, + "y": 142, + "width": 10, + "height": 6, + "strokeColor": "transparent", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-shadow", + "type": "rectangle", + "x": 582, + "y": 90, + "width": 260, + "height": 108, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2", + "type": "rectangle", + "x": 580, + "y": 88, + "width": 260, + "height": 108, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-label", + "type": "text", + "x": 604, + "y": 112, + "width": 100, + "height": 16, + "text": "Organizations", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-value", + "type": "text", + "x": 604, + "y": 140, + "width": 60, + "height": 36, + "text": "56", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-trend", + "type": "text", + "x": 768, + "y": 148, + "width": 56, + "height": 14, + "text": "+6%", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "#22c55e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-trend-arrow", + "type": "rectangle", + "x": 810, + "y": 142, + "width": 10, + "height": 6, + "strokeColor": "transparent", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-shadow", + "type": "rectangle", + "x": 858, + "y": 90, + "width": 260, + "height": 108, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3", + "type": "rectangle", + "x": 856, + "y": 88, + "width": 260, + "height": 108, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-label", + "type": "text", + "x": 880, + "y": 112, + "width": 100, + "height": 16, + "text": "Monthly Revenue", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-value", + "type": "text", + "x": 880, + "y": 140, + "width": 120, + "height": 36, + "text": "$12,400", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#22c55e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-trend", + "type": "text", + "x": 1040, + "y": 148, + "width": 60, + "height": 14, + "text": "+32%", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "#22c55e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-trend-arrow", + "type": "rectangle", + "x": 1086, + "y": 142, + "width": 10, + "height": 6, + "strokeColor": "transparent", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-shadow", + "type": "rectangle", + "x": 1134, + "y": 90, + "width": 260, + "height": 108, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4", + "type": "rectangle", + "x": 1132, + "y": 88, + "width": 260, + "height": 108, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-label", + "type": "text", + "x": 1156, + "y": 112, + "width": 100, + "height": 16, + "text": "Active Sessions", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-value", + "type": "text", + "x": 1156, + "y": 140, + "width": 80, + "height": 36, + "text": "342", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-trend", + "type": "text", + "x": 1322, + "y": 148, + "width": 56, + "height": 14, + "text": "-3%", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-trend-arrow", + "type": "rectangle", + "x": 1362, + "y": 150, + "width": 10, + "height": 6, + "strokeColor": "transparent", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-card-shadow", + "type": "rectangle", + "x": 306, + "y": 222, + "width": 700, + "height": 460, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-card", + "type": "rectangle", + "x": 304, + "y": 220, + "width": 700, + "height": 460, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-card-title", + "type": "text", + "x": 328, + "y": 244, + "width": 140, + "height": 22, + "text": "Recent Activity", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-card-view-all", + "type": "text", + "x": 920, + "y": 248, + "width": 60, + "height": 14, + "text": "View all", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-1", + "type": "rectangle", + "x": 328, + "y": 284, + "width": 652, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-1-avatar", + "type": "rectangle", + "x": 344, + "y": 300, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-1-avatar-initials", + "type": "text", + "x": 351, + "y": 305, + "width": 10, + "height": 14, + "text": "J", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-1-icon", + "type": "rectangle", + "x": 380, + "y": 302, + "width": 20, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-1-text", + "type": "text", + "x": 412, + "y": 300, + "width": 300, + "height": 16, + "text": "User signed up - john@example.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-1-time", + "type": "text", + "x": 912, + "y": 304, + "width": 60, + "height": 14, + "text": "2m ago", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-2", + "type": "rectangle", + "x": 328, + "y": 356, + "width": 652, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-2-avatar", + "type": "rectangle", + "x": 344, + "y": 372, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-2-avatar-initials", + "type": "text", + "x": 351, + "y": 377, + "width": 10, + "height": 14, + "text": "A", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-2-icon", + "type": "rectangle", + "x": 380, + "y": 374, + "width": 20, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-2-text", + "type": "text", + "x": 412, + "y": 372, + "width": 280, + "height": 16, + "text": "New org created - Acme Corp", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-2-time", + "type": "text", + "x": 912, + "y": 376, + "width": 60, + "height": 14, + "text": "5m ago", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-3", + "type": "rectangle", + "x": 328, + "y": 428, + "width": 652, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-3-avatar", + "type": "rectangle", + "x": 344, + "y": 444, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-3-avatar-initials", + "type": "text", + "x": 352, + "y": 449, + "width": 8, + "height": 14, + "text": "$", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-3-icon", + "type": "rectangle", + "x": 380, + "y": 446, + "width": 20, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-3-text", + "type": "text", + "x": 412, + "y": 444, + "width": 320, + "height": 16, + "text": "Subscription upgraded - Pro Plan ($99/mo)", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-3-time", + "type": "text", + "x": 912, + "y": 448, + "width": 60, + "height": 14, + "text": "12m ago", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-4", + "type": "rectangle", + "x": 328, + "y": 500, + "width": 652, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-4-avatar", + "type": "rectangle", + "x": 344, + "y": 516, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-4-avatar-initials", + "type": "text", + "x": 351, + "y": 521, + "width": 10, + "height": 14, + "text": "T", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-4-icon", + "type": "rectangle", + "x": 380, + "y": 518, + "width": 20, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-4-text", + "type": "text", + "x": 412, + "y": 516, + "width": 260, + "height": 16, + "text": "User invited - team@acme.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-4-time", + "type": "text", + "x": 912, + "y": 520, + "width": 60, + "height": 14, + "text": "25m ago", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-5", + "type": "rectangle", + "x": 328, + "y": 572, + "width": 652, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-5-avatar", + "type": "rectangle", + "x": 344, + "y": 588, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-5-avatar-initials", + "type": "text", + "x": 352, + "y": 593, + "width": 8, + "height": 14, + "text": "!", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-5-icon", + "type": "rectangle", + "x": 380, + "y": 590, + "width": 20, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-5-text", + "type": "text", + "x": 412, + "y": 588, + "width": 340, + "height": 16, + "text": "API rate limit exceeded - user_123", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-5-time", + "type": "text", + "x": 912, + "y": 592, + "width": 60, + "height": 14, + "text": "1h ago", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-actions-card-shadow", + "type": "rectangle", + "x": 1026, + "y": 222, + "width": 368, + "height": 460, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-actions-card", + "type": "rectangle", + "x": 1024, + "y": 220, + "width": 368, + "height": 460, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-actions-title", + "type": "text", + "x": 1048, + "y": 244, + "width": 120, + "height": 22, + "text": "Quick Actions", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-1", + "type": "rectangle", + "x": 1048, + "y": 284, + "width": 320, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-1-icon", + "type": "rectangle", + "x": 1068, + "y": 298, + "width": 20, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-1-text", + "type": "text", + "x": 1100, + "y": 300, + "width": 84, + "height": 16, + "text": "Add User", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-2", + "type": "rectangle", + "x": 1048, + "y": 348, + "width": 320, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-2-icon", + "type": "rectangle", + "x": 1068, + "y": 362, + "width": 20, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 20, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-2-text", + "type": "text", + "x": 1100, + "y": 364, + "width": 108, + "height": 16, + "text": "View Reports", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-3", + "type": "rectangle", + "x": 1048, + "y": 412, + "width": 320, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-3-icon", + "type": "rectangle", + "x": 1068, + "y": 426, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-3-text", + "type": "text", + "x": 1100, + "y": 428, + "width": 76, + "height": 16, + "text": "Settings", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-4", + "type": "rectangle", + "x": 1048, + "y": 476, + "width": 320, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-4-icon", + "type": "rectangle", + "x": 1068, + "y": 490, + "width": 20, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 20, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-4-text", + "type": "text", + "x": 1100, + "y": 492, + "width": 100, + "height": 16, + "text": "Audit Logs", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-5", + "type": "rectangle", + "x": 1048, + "y": 540, + "width": 320, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-5-icon", + "type": "rectangle", + "x": 1068, + "y": 554, + "width": 20, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-5-text", + "type": "text", + "x": 1100, + "y": 556, + "width": 140, + "height": 16, + "text": "System Maintenance", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/high/dashboard-org.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/high/dashboard-org.excalidraw new file mode 100644 index 0000000..f1db434 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/high/dashboard-org.excalidraw @@ -0,0 +1,2700 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 24, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-text", + "type": "text", + "x": 36, + "y": 24, + "width": 96, + "height": 18, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-overview", + "type": "rectangle", + "x": 24, + "y": 100, + "width": 232, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-overview-icon", + "type": "rectangle", + "x": 36, + "y": 110, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-overview-text", + "type": "text", + "x": 68, + "y": 112, + "width": 80, + "height": 16, + "text": "Overview", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members", + "type": "rectangle", + "x": 24, + "y": 156, + "width": 232, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-icon", + "type": "rectangle", + "x": 36, + "y": 166, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-text", + "type": "text", + "x": 68, + "y": 168, + "width": 70, + "height": 16, + "text": "Members", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings", + "type": "rectangle", + "x": 24, + "y": 212, + "width": 232, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-icon", + "type": "rectangle", + "x": 36, + "y": 222, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-text", + "type": "text", + "x": 68, + "y": 224, + "width": 60, + "height": 16, + "text": "Settings", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing", + "type": "rectangle", + "x": 24, + "y": 268, + "width": 232, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing-icon", + "type": "rectangle", + "x": 36, + "y": 278, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing-text", + "type": "text", + "x": 68, + "y": 280, + "width": 50, + "height": 16, + "text": "Billing", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 24, + "y": 852, + "width": 232, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-avatar", + "type": "rectangle", + "x": 32, + "y": 858, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-avatar-initials", + "type": "text", + "x": 37, + "y": 861, + "width": 10, + "height": 14, + "text": "A", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-name", + "type": "text", + "x": 60, + "y": 860, + "width": 80, + "height": 14, + "text": "Acme Inc.", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "text", + "x": 304, + "y": 20, + "width": 200, + "height": 24, + "text": "Organization Overview", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-time-selector", + "type": "rectangle", + "x": 1240, + "y": 16, + "width": 128, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-time-selector-text", + "type": "text", + "x": 1256, + "y": 24, + "width": 80, + "height": 16, + "text": "Last 7 days", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-time-selector-arrow", + "type": "rectangle", + "x": 1348, + "y": 24, + "width": 12, + "height": 6, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-user-avatar", + "type": "rectangle", + "x": 1388, + "y": 16, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 16 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-shadow", + "type": "rectangle", + "x": 306, + "y": 90, + "width": 260, + "height": 108, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 260, + "height": 108, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-label", + "type": "text", + "x": 328, + "y": 112, + "width": 100, + "height": 16, + "text": "Total Members", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-value", + "type": "text", + "x": 328, + "y": 140, + "width": 80, + "height": 36, + "text": "24", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-trend", + "type": "text", + "x": 488, + "y": 148, + "width": 60, + "height": 14, + "text": "+12%", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "#22c55e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-trend-arrow", + "type": "rectangle", + "x": 534, + "y": 142, + "width": 10, + "height": 6, + "strokeColor": "transparent", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-shadow", + "type": "rectangle", + "x": 582, + "y": 90, + "width": 260, + "height": 108, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2", + "type": "rectangle", + "x": 580, + "y": 88, + "width": 260, + "height": 108, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-label", + "type": "text", + "x": 604, + "y": 112, + "width": 100, + "height": 16, + "text": "Active Users", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-value", + "type": "text", + "x": 604, + "y": 140, + "width": 60, + "height": 36, + "text": "18", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-trend", + "type": "text", + "x": 768, + "y": 148, + "width": 56, + "height": 14, + "text": "+8%", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "#22c55e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-trend-arrow", + "type": "rectangle", + "x": 810, + "y": 142, + "width": 10, + "height": 6, + "strokeColor": "transparent", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-shadow", + "type": "rectangle", + "x": 858, + "y": 90, + "width": 260, + "height": 108, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3", + "type": "rectangle", + "x": 856, + "y": 88, + "width": 260, + "height": 108, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-label", + "type": "text", + "x": 880, + "y": 112, + "width": 80, + "height": 16, + "text": "API Calls", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-value", + "type": "text", + "x": 880, + "y": 140, + "width": 100, + "height": 36, + "text": "12,500", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-trend", + "type": "text", + "x": 1040, + "y": 148, + "width": 60, + "height": 14, + "text": "+24%", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "#22c55e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-trend-arrow", + "type": "rectangle", + "x": 1086, + "y": 142, + "width": 10, + "height": 6, + "strokeColor": "transparent", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-shadow", + "type": "rectangle", + "x": 1134, + "y": 90, + "width": 260, + "height": 108, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4", + "type": "rectangle", + "x": 1132, + "y": 88, + "width": 260, + "height": 108, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-label", + "type": "text", + "x": 1156, + "y": 112, + "width": 100, + "height": 16, + "text": "Monthly Cost", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-value", + "type": "text", + "x": 1156, + "y": 140, + "width": 100, + "height": 36, + "text": "$2,400", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-trend", + "type": "text", + "x": 1322, + "y": 148, + "width": 56, + "height": 14, + "text": "-5%", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-trend-arrow", + "type": "rectangle", + "x": 1362, + "y": 150, + "width": 10, + "height": 6, + "strokeColor": "transparent", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-card-shadow", + "type": "rectangle", + "x": 306, + "y": 222, + "width": 1086, + "height": 320, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-card", + "type": "rectangle", + "x": 304, + "y": 220, + "width": 1086, + "height": 320, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-title", + "type": "text", + "x": 328, + "y": 244, + "width": 140, + "height": 22, + "text": "Usage Over Time", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-area", + "type": "rectangle", + "x": 328, + "y": 284, + "width": 1038, + "height": 232, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-y-axis-5k", + "type": "text", + "x": 340, + "y": 296, + "width": 30, + "height": 12, + "text": "5k", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-y-axis-4k", + "type": "text", + "x": 340, + "y": 346, + "width": 30, + "height": 12, + "text": "4k", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-y-axis-3k", + "type": "text", + "x": 340, + "y": 396, + "width": 30, + "height": 12, + "text": "3k", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-y-axis-2k", + "type": "text", + "x": 340, + "y": 446, + "width": 30, + "height": 12, + "text": "2k", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-grid-1", + "type": "rectangle", + "x": 378, + "y": 300, + "width": 976, + "height": 1, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 20, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-grid-2", + "type": "rectangle", + "x": 378, + "y": 350, + "width": 976, + "height": 1, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 20, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-grid-3", + "type": "rectangle", + "x": 378, + "y": 400, + "width": 976, + "height": 1, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 20, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-grid-4", + "type": "rectangle", + "x": 378, + "y": 450, + "width": 976, + "height": 1, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 20, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-area-fill", + "type": "rectangle", + "x": 378, + "y": 350, + "width": 976, + "height": 140, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 15, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-line", + "type": "rectangle", + "x": 378, + "y": 350, + "width": 976, + "height": 3, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-point-1", + "type": "rectangle", + "x": 438, + "y": 376, + "width": 8, + "height": 8, + "strokeColor": "#ffffff", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-point-2", + "type": "rectangle", + "x": 598, + "y": 346, + "width": 8, + "height": 8, + "strokeColor": "#ffffff", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-point-3", + "type": "rectangle", + "x": 758, + "y": 330, + "width": 8, + "height": 8, + "strokeColor": "#ffffff", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-point-4", + "type": "rectangle", + "x": 918, + "y": 360, + "width": 8, + "height": 8, + "strokeColor": "#ffffff", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-point-5", + "type": "rectangle", + "x": 1078, + "y": 320, + "width": 8, + "height": 8, + "strokeColor": "#ffffff", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-point-6", + "type": "rectangle", + "x": 1238, + "y": 310, + "width": 8, + "height": 8, + "strokeColor": "#ffffff", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-point-7", + "type": "rectangle", + "x": 1338, + "y": 298, + "width": 8, + "height": 8, + "strokeColor": "#ffffff", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-axis-mon", + "type": "text", + "x": 428, + "y": 498, + "width": 30, + "height": 12, + "text": "Mon", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-axis-tue", + "type": "text", + "x": 588, + "y": 498, + "width": 30, + "height": 12, + "text": "Tue", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-axis-wed", + "type": "text", + "x": 748, + "y": 498, + "width": 30, + "height": 12, + "text": "Wed", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-axis-thu", + "type": "text", + "x": 908, + "y": 498, + "width": 30, + "height": 12, + "text": "Thu", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-axis-fri", + "type": "text", + "x": 1068, + "y": 498, + "width": 30, + "height": 12, + "text": "Fri", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-axis-sat", + "type": "text", + "x": 1228, + "y": 498, + "width": 30, + "height": 12, + "text": "Sat", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-axis-sun", + "type": "text", + "x": 1328, + "y": 498, + "width": 30, + "height": 12, + "text": "Sun", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-shadow", + "type": "rectangle", + "x": 306, + "y": 566, + "width": 532, + "height": 280, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1", + "type": "rectangle", + "x": 304, + "y": 564, + "width": 532, + "height": 280, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-title", + "type": "text", + "x": 328, + "y": 588, + "width": 120, + "height": 22, + "text": "Usage by Tool", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-area", + "type": "rectangle", + "x": 328, + "y": 624, + "width": 484, + "height": 196, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-1", + "type": "rectangle", + "x": 368, + "y": 716, + "width": 48, + "height": 80, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-2", + "type": "rectangle", + "x": 440, + "y": 668, + "width": 48, + "height": 128, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-3", + "type": "rectangle", + "x": 512, + "y": 744, + "width": 48, + "height": 52, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-4", + "type": "rectangle", + "x": 584, + "y": 648, + "width": 48, + "height": 148, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-5", + "type": "rectangle", + "x": 656, + "y": 696, + "width": 48, + "height": 100, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-6", + "type": "rectangle", + "x": 728, + "y": 726, + "width": 48, + "height": 70, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-shadow", + "type": "rectangle", + "x": 856, + "y": 566, + "width": 532, + "height": 280, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2", + "type": "rectangle", + "x": 854, + "y": 564, + "width": 532, + "height": 280, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-title", + "type": "text", + "x": 878, + "y": 588, + "width": 120, + "height": 22, + "text": "Distribution", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-donut-outer", + "type": "rectangle", + "x": 948, + "y": 644, + "width": 160, + "height": 160, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 80 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-donut-seg2", + "type": "rectangle", + "x": 948, + "y": 724, + "width": 80, + "height": 80, + "strokeColor": "transparent", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-donut-seg3", + "type": "rectangle", + "x": 1028, + "y": 764, + "width": 80, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-donut-inner", + "type": "rectangle", + "x": 988, + "y": 684, + "width": 80, + "height": 80, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 40 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-donut-center-value", + "type": "text", + "x": 1004, + "y": 712, + "width": 48, + "height": 24, + "text": "100%", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-legend-1-box", + "type": "rectangle", + "x": 1148, + "y": 672, + "width": 16, + "height": 16, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-legend-1-text", + "type": "text", + "x": 1172, + "y": 674, + "width": 80, + "height": 14, + "text": "Chat 45%", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-legend-2-box", + "type": "rectangle", + "x": 1148, + "y": 704, + "width": 16, + "height": 16, + "strokeColor": "transparent", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-legend-2-text", + "type": "text", + "x": 1172, + "y": 706, + "width": 80, + "height": 14, + "text": "Image 35%", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-legend-3-box", + "type": "rectangle", + "x": 1148, + "y": 736, + "width": 16, + "height": 16, + "strokeColor": "transparent", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-legend-3-text", + "type": "text", + "x": 1172, + "y": 738, + "width": 80, + "height": 14, + "text": "PDF 20%", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/high/dashboard-user.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/high/dashboard-user.excalidraw new file mode 100644 index 0000000..bd3b4a8 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/high/dashboard-user.excalidraw @@ -0,0 +1,1936 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 24, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-text", + "type": "text", + "x": 36, + "y": 24, + "width": 96, + "height": 18, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard", + "type": "rectangle", + "x": 24, + "y": 100, + "width": 232, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-icon", + "type": "rectangle", + "x": 36, + "y": 110, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-icon-grid", + "type": "rectangle", + "x": 40, + "y": 114, + "width": 5, + "height": 5, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 1 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-icon-grid-2", + "type": "rectangle", + "x": 47, + "y": 114, + "width": 5, + "height": 5, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 1 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-icon-grid-3", + "type": "rectangle", + "x": 40, + "y": 121, + "width": 5, + "height": 5, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 1 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-icon-grid-4", + "type": "rectangle", + "x": 47, + "y": 121, + "width": 5, + "height": 5, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 1 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-text", + "type": "text", + "x": 68, + "y": 112, + "width": 80, + "height": 16, + "text": "Dashboard", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-aitools", + "type": "rectangle", + "x": 24, + "y": 156, + "width": 232, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-aitools-icon", + "type": "rectangle", + "x": 36, + "y": 166, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-aitools-text", + "type": "text", + "x": 68, + "y": 168, + "width": 60, + "height": 16, + "text": "AI Tools", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings", + "type": "rectangle", + "x": 24, + "y": 212, + "width": 232, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-icon", + "type": "rectangle", + "x": 36, + "y": 222, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-text", + "type": "text", + "x": 68, + "y": 224, + "width": 60, + "height": 16, + "text": "Settings", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-support-item", + "type": "rectangle", + "x": 24, + "y": 720, + "width": 232, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-support-icon", + "type": "rectangle", + "x": 36, + "y": 730, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-support-text", + "type": "text", + "x": 68, + "y": 732, + "width": 60, + "height": 16, + "text": "Support", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-feedback-item", + "type": "rectangle", + "x": 24, + "y": 776, + "width": 232, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-feedback-icon", + "type": "rectangle", + "x": 36, + "y": 786, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-feedback-text", + "type": "text", + "x": 68, + "y": 788, + "width": 70, + "height": 16, + "text": "Feedback", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 24, + "y": 852, + "width": 232, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-avatar", + "type": "rectangle", + "x": 32, + "y": 858, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-avatar-initials", + "type": "text", + "x": 37, + "y": 861, + "width": 10, + "height": 14, + "text": "J", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-name", + "type": "text", + "x": 60, + "y": 860, + "width": 80, + "height": 14, + "text": "John Doe", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "text", + "x": 304, + "y": 20, + "width": 120, + "height": 24, + "text": "Dashboard", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-user-avatar", + "type": "rectangle", + "x": 1388, + "y": 16, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 16 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-user-avatar-initials", + "type": "text", + "x": 1397, + "y": 24, + "width": 14, + "height": 16, + "text": "JD", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-card-shadow", + "type": "rectangle", + "x": 306, + "y": 90, + "width": 1112, + "height": 120, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-card", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 1112, + "height": 120, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-title", + "type": "text", + "x": 328, + "y": 112, + "width": 260, + "height": 28, + "text": "Welcome back, John!", + "fontSize": 24, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-subtitle", + "type": "text", + "x": 328, + "y": 148, + "width": 280, + "height": 18, + "text": "Get started with our AI-powered tools", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-subtitle-2", + "type": "text", + "x": 328, + "y": 172, + "width": 360, + "height": 14, + "text": "Explore chat, image generation, and document tools below.", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-shadow", + "type": "rectangle", + "x": 306, + "y": 234, + "width": 352, + "height": 220, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1", + "type": "rectangle", + "x": 304, + "y": 232, + "width": 352, + "height": 220, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-icon-bg", + "type": "rectangle", + "x": 328, + "y": 256, + "width": 56, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-icon-bubble", + "type": "rectangle", + "x": 340, + "y": 268, + "width": 32, + "height": 24, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-icon-tail", + "type": "rectangle", + "x": 344, + "y": 288, + "width": 8, + "height": 8, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-title", + "type": "text", + "x": 328, + "y": 328, + "width": 100, + "height": 22, + "text": "AI Chat", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-desc", + "type": "text", + "x": 328, + "y": 358, + "width": 300, + "height": 18, + "text": "Start conversations with AI assistants", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-desc-2", + "type": "text", + "x": 328, + "y": 380, + "width": 280, + "height": 14, + "text": "Ask questions, get help, brainstorm ideas.", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-button", + "type": "rectangle", + "x": 328, + "y": 408, + "width": 100, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-button-text", + "type": "text", + "x": 352, + "y": 418, + "width": 52, + "height": 16, + "text": "Open", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-shadow", + "type": "rectangle", + "x": 678, + "y": 234, + "width": 352, + "height": 220, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2", + "type": "rectangle", + "x": 676, + "y": 232, + "width": 352, + "height": 220, + "strokeColor": "#f97316", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-new-badge", + "type": "rectangle", + "x": 968, + "y": 244, + "width": 44, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-new-badge-text", + "type": "text", + "x": 976, + "y": 248, + "width": 28, + "height": 12, + "text": "New", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-icon-bg", + "type": "rectangle", + "x": 700, + "y": 256, + "width": 56, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-icon-frame", + "type": "rectangle", + "x": 712, + "y": 268, + "width": 32, + "height": 32, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-icon-mountain", + "type": "rectangle", + "x": 718, + "y": 284, + "width": 20, + "height": 10, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-icon-sun", + "type": "rectangle", + "x": 734, + "y": 274, + "width": 6, + "height": 6, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 3 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-title", + "type": "text", + "x": 700, + "y": 328, + "width": 160, + "height": 22, + "text": "Image Generation", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-desc", + "type": "text", + "x": 700, + "y": 358, + "width": 280, + "height": 18, + "text": "Create stunning images from text", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-desc-2", + "type": "text", + "x": 700, + "y": 380, + "width": 260, + "height": 14, + "text": "Generate artwork, illustrations, and photos.", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-button", + "type": "rectangle", + "x": 700, + "y": 408, + "width": 100, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-button-text", + "type": "text", + "x": 718, + "y": 418, + "width": 64, + "height": 16, + "text": "Try Now", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-shadow", + "type": "rectangle", + "x": 1050, + "y": 234, + "width": 352, + "height": 220, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3", + "type": "rectangle", + "x": 1048, + "y": 232, + "width": 352, + "height": 220, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-icon-bg", + "type": "rectangle", + "x": 1072, + "y": 256, + "width": 56, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-icon-doc", + "type": "rectangle", + "x": 1086, + "y": 266, + "width": 28, + "height": 36, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-icon-doc-line1", + "type": "rectangle", + "x": 1090, + "y": 274, + "width": 20, + "height": 3, + "strokeColor": "transparent", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 1 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-icon-doc-line2", + "type": "rectangle", + "x": 1090, + "y": 280, + "width": 16, + "height": 3, + "strokeColor": "transparent", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 1 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-icon-doc-line3", + "type": "rectangle", + "x": 1090, + "y": 286, + "width": 20, + "height": 3, + "strokeColor": "transparent", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 1 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-icon-doc-line4", + "type": "rectangle", + "x": 1090, + "y": 292, + "width": 14, + "height": 3, + "strokeColor": "transparent", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 1 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-title", + "type": "text", + "x": 1072, + "y": 328, + "width": 100, + "height": 22, + "text": "PDF Tools", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-desc", + "type": "text", + "x": 1072, + "y": 358, + "width": 280, + "height": 18, + "text": "Chat with your documents", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-desc-2", + "type": "text", + "x": 1072, + "y": 380, + "width": 300, + "height": 14, + "text": "Upload PDFs and ask questions about them.", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-button", + "type": "rectangle", + "x": 1072, + "y": 408, + "width": 100, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-button-text", + "type": "text", + "x": 1092, + "y": 418, + "width": 60, + "height": 16, + "text": "Upload", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/high/data-table-invitations.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/high/data-table-invitations.excalidraw new file mode 100644 index 0000000..ba69206 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/high/data-table-invitations.excalidraw @@ -0,0 +1,3360 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-switcher", + "type": "rectangle", + "x": 20, + "y": 80, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-avatar", + "type": "rectangle", + "x": 32, + "y": 88, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-name", + "type": "text", + "x": 64, + "y": 92, + "width": 100, + "height": 16, + "text": "Acme Inc", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-chevron", + "type": "line", + "x": 240, + "y": 96, + "width": 8, + "height": 5, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 4, + 5 + ], + [ + 8, + 0 + ] + ] + }, + { + "id": "sidebar-section-label", + "type": "text", + "x": 20, + "y": 140, + "width": 80, + "height": 12, + "text": "WORKSPACE", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-icon", + "type": "rectangle", + "x": 32, + "y": 170, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-label", + "type": "text", + "x": 60, + "y": 172, + "width": 80, + "height": 16, + "text": "Dashboard", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-active", + "type": "rectangle", + "x": 20, + "y": 208, + "width": 240, + "height": 40, + "strokeColor": "#f97316", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-icon", + "type": "ellipse", + "x": 32, + "y": 218, + "width": 20, + "height": 20, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-label", + "type": "text", + "x": 60, + "y": 220, + "width": 60, + "height": 16, + "text": "Members", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings", + "type": "rectangle", + "x": 20, + "y": 256, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-icon", + "type": "rectangle", + "x": 32, + "y": 266, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-label", + "type": "text", + "x": 60, + "y": 268, + "width": 60, + "height": 16, + "text": "Settings", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing", + "type": "rectangle", + "x": 20, + "y": 304, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing-icon", + "type": "rectangle", + "x": 32, + "y": 314, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing-label", + "type": "text", + "x": 60, + "y": 316, + "width": 50, + "height": 16, + "text": "Billing", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-avatar", + "type": "ellipse", + "x": 28, + "y": 856, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-name", + "type": "text", + "x": 60, + "y": 860, + "width": 80, + "height": 16, + "text": "John Doe", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "text", + "x": 304, + "y": 20, + "width": 80, + "height": 24, + "text": "Members", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invite-button", + "type": "rectangle", + "x": 1260, + "y": 16, + "width": 156, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invite-icon", + "type": "line", + "x": 1276, + "y": 26, + "width": 12, + "height": 12, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 6, + 0 + ], + [ + 6, + 12 + ] + ] + }, + { + "id": "header-invite-icon2", + "type": "line", + "x": 1276, + "y": 26, + "width": 12, + "height": 12, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 6 + ], + [ + 12, + 6 + ] + ] + }, + { + "id": "header-invite-text", + "type": "text", + "x": 1296, + "y": 24, + "width": 100, + "height": 16, + "text": "Invite Member", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tabs-container", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 1112, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-members", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 120, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-members-label", + "type": "text", + "x": 316, + "y": 104, + "width": 96, + "height": 16, + "text": "Members (24)", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "tabs-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-invitations-active", + "type": "rectangle", + "x": 424, + "y": 88, + "width": 180, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-invitations-label", + "type": "text", + "x": 436, + "y": 104, + "width": 156, + "height": 16, + "text": "Pending Invitations (3)", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-invitations-underline", + "type": "rectangle", + "x": 424, + "y": 132, + "width": 180, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-container", + "type": "rectangle", + "x": 304, + "y": 152, + "width": 1112, + "height": 716, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "toolbar", + "type": "rectangle", + "x": 304, + "y": 152, + "width": 1112, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-input", + "type": "rectangle", + "x": 316, + "y": 162, + "width": 260, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-icon", + "type": "ellipse", + "x": 326, + "y": 170, + "width": 16, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-icon-handle", + "type": "line", + "x": 339, + "y": 183, + "width": 6, + "height": 6, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 6, + 6 + ] + ] + }, + { + "id": "search-placeholder", + "type": "text", + "x": 350, + "y": 172, + "width": 160, + "height": 16, + "text": "Search invitations...", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-status", + "type": "rectangle", + "x": 588, + "y": 162, + "width": 100, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-status-text", + "type": "text", + "x": 600, + "y": 172, + "width": 50, + "height": 16, + "text": "Status", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-status-chevron", + "type": "line", + "x": 668, + "y": 176, + "width": 8, + "height": 5, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 4, + 5 + ], + [ + 8, + 0 + ] + ] + }, + { + "id": "filter-role", + "type": "rectangle", + "x": 700, + "y": 162, + "width": 100, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role-text", + "type": "text", + "x": 712, + "y": 172, + "width": 40, + "height": 16, + "text": "Role", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role-chevron", + "type": "line", + "x": 780, + "y": 176, + "width": 8, + "height": 5, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 4, + 5 + ], + [ + 8, + 0 + ] + ] + }, + { + "id": "table-header-row", + "type": "rectangle", + "x": 304, + "y": 208, + "width": 1112, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-email-col", + "type": "text", + "x": 320, + "y": 224, + "width": 50, + "height": 14, + "text": "Email", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-role-col", + "type": "text", + "x": 480, + "y": 224, + "width": 40, + "height": 14, + "text": "Role", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invitedby-col", + "type": "text", + "x": 600, + "y": 224, + "width": 70, + "height": 14, + "text": "Invited by", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-sent-col", + "type": "text", + "x": 760, + "y": 224, + "width": 40, + "height": 14, + "text": "Sent", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-expires-col", + "type": "text", + "x": 880, + "y": 224, + "width": 55, + "height": 14, + "text": "Expires", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-status-col", + "type": "text", + "x": 1000, + "y": 224, + "width": 50, + "height": 14, + "text": "Status", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-actions-col", + "type": "text", + "x": 1160, + "y": 224, + "width": 55, + "height": 14, + "text": "Actions", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-1", + "type": "rectangle", + "x": 304, + "y": 256, + "width": 1112, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-email", + "type": "text", + "x": 320, + "y": 277, + "width": 140, + "height": 14, + "text": "alice@example.com", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-badge", + "type": "rectangle", + "x": 480, + "y": 274, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-text", + "type": "text", + "x": 490, + "y": 277, + "width": 45, + "height": 14, + "text": "Member", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-invitedby-avatar", + "type": "ellipse", + "x": 600, + "y": 272, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-invitedby-initials", + "type": "text", + "x": 606, + "y": 278, + "width": 12, + "height": 12, + "text": "JD", + "fontSize": 9, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-invitedby", + "type": "text", + "x": 632, + "y": 277, + "width": 80, + "height": 14, + "text": "John Doe", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-sent", + "type": "text", + "x": 760, + "y": 277, + "width": 75, + "height": 14, + "text": "Jan 20, 2024", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-expires", + "type": "text", + "x": 880, + "y": 277, + "width": 75, + "height": 14, + "text": "Jan 27, 2024", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status-badge", + "type": "rectangle", + "x": 1000, + "y": 273, + "width": 66, + "height": 22, + "strokeColor": "transparent", + "backgroundColor": "#facc15", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 25, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 11 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status-dot", + "type": "ellipse", + "x": 1010, + "y": 280, + "width": 8, + "height": 8, + "strokeColor": "transparent", + "backgroundColor": "#facc15", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status-text", + "type": "text", + "x": 1022, + "y": 277, + "width": 40, + "height": 14, + "text": "Pending", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ca8a04", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-resend", + "type": "rectangle", + "x": 1160, + "y": 272, + "width": 70, + "height": 26, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-resend-icon", + "type": "ellipse", + "x": 1168, + "y": 280, + "width": 12, + "height": 12, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-resend-text", + "type": "text", + "x": 1184, + "y": 278, + "width": 40, + "height": 14, + "text": "Resend", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-cancel", + "type": "rectangle", + "x": 1240, + "y": 272, + "width": 70, + "height": 26, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-cancel-icon-x1", + "type": "line", + "x": 1250, + "y": 280, + "width": 8, + "height": 8, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 8, + 8 + ] + ] + }, + { + "id": "row-1-action-cancel-icon-x2", + "type": "line", + "x": 1250, + "y": 288, + "width": 8, + "height": 8, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 8, + -8 + ] + ] + }, + { + "id": "row-1-action-cancel-text", + "type": "text", + "x": 1264, + "y": 278, + "width": 40, + "height": 14, + "text": "Cancel", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-2-hover", + "type": "rectangle", + "x": 304, + "y": 312, + "width": 1112, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 40, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-email", + "type": "text", + "x": 320, + "y": 333, + "width": 150, + "height": 14, + "text": "charlie@example.com", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-badge", + "type": "rectangle", + "x": 480, + "y": 330, + "width": 55, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 25, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-text", + "type": "text", + "x": 492, + "y": 333, + "width": 35, + "height": 14, + "text": "Admin", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-invitedby-avatar", + "type": "ellipse", + "x": 600, + "y": 328, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-invitedby-initials", + "type": "text", + "x": 607, + "y": 334, + "width": 10, + "height": 12, + "text": "JS", + "fontSize": 9, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-invitedby", + "type": "text", + "x": 632, + "y": 333, + "width": 80, + "height": 14, + "text": "Jane Smith", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-sent", + "type": "text", + "x": 760, + "y": 333, + "width": 75, + "height": 14, + "text": "Jan 15, 2024", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-expires", + "type": "text", + "x": 880, + "y": 333, + "width": 75, + "height": 14, + "text": "Jan 22, 2024", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status-badge", + "type": "rectangle", + "x": 1000, + "y": 329, + "width": 62, + "height": 22, + "strokeColor": "transparent", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 25, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 11 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status-dot", + "type": "ellipse", + "x": 1010, + "y": 336, + "width": 8, + "height": 8, + "strokeColor": "transparent", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status-text", + "type": "text", + "x": 1022, + "y": 333, + "width": 38, + "height": 14, + "text": "Expired", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-resend", + "type": "rectangle", + "x": 1160, + "y": 328, + "width": 70, + "height": 26, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-resend-icon", + "type": "ellipse", + "x": 1168, + "y": 336, + "width": 12, + "height": 12, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-resend-text", + "type": "text", + "x": 1184, + "y": 334, + "width": 40, + "height": 14, + "text": "Resend", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-cancel", + "type": "rectangle", + "x": 1240, + "y": 328, + "width": 70, + "height": 26, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-cancel-icon-x1", + "type": "line", + "x": 1250, + "y": 336, + "width": 8, + "height": 8, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 8, + 8 + ] + ] + }, + { + "id": "row-2-action-cancel-icon-x2", + "type": "line", + "x": 1250, + "y": 344, + "width": 8, + "height": 8, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 8, + -8 + ] + ] + }, + { + "id": "row-2-action-cancel-text", + "type": "text", + "x": 1264, + "y": 334, + "width": 40, + "height": 14, + "text": "Cancel", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-3", + "type": "rectangle", + "x": 304, + "y": 368, + "width": 1112, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-email", + "type": "text", + "x": 320, + "y": 389, + "width": 130, + "height": 14, + "text": "dave@example.com", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-badge", + "type": "rectangle", + "x": 480, + "y": 386, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-text", + "type": "text", + "x": 490, + "y": 389, + "width": 45, + "height": 14, + "text": "Member", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-invitedby-avatar", + "type": "ellipse", + "x": 600, + "y": 384, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-invitedby-initials", + "type": "text", + "x": 606, + "y": 390, + "width": 12, + "height": 12, + "text": "JD", + "fontSize": 9, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-invitedby", + "type": "text", + "x": 632, + "y": 389, + "width": 80, + "height": 14, + "text": "John Doe", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-sent", + "type": "text", + "x": 760, + "y": 389, + "width": 75, + "height": 14, + "text": "Jan 25, 2024", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-expires", + "type": "text", + "x": 880, + "y": 389, + "width": 75, + "height": 14, + "text": "Feb 1, 2024", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-status-badge", + "type": "rectangle", + "x": 1000, + "y": 385, + "width": 66, + "height": 22, + "strokeColor": "transparent", + "backgroundColor": "#facc15", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 25, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 11 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-status-dot", + "type": "ellipse", + "x": 1010, + "y": 392, + "width": 8, + "height": 8, + "strokeColor": "transparent", + "backgroundColor": "#facc15", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-status-text", + "type": "text", + "x": 1022, + "y": 389, + "width": 40, + "height": 14, + "text": "Pending", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ca8a04", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-resend", + "type": "rectangle", + "x": 1160, + "y": 384, + "width": 70, + "height": 26, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-resend-icon", + "type": "ellipse", + "x": 1168, + "y": 392, + "width": 12, + "height": 12, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-resend-text", + "type": "text", + "x": 1184, + "y": 390, + "width": 40, + "height": 14, + "text": "Resend", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-cancel", + "type": "rectangle", + "x": 1240, + "y": 384, + "width": 70, + "height": 26, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-cancel-icon-x1", + "type": "line", + "x": 1250, + "y": 392, + "width": 8, + "height": 8, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 8, + 8 + ] + ] + }, + { + "id": "row-3-action-cancel-icon-x2", + "type": "line", + "x": 1250, + "y": 400, + "width": 8, + "height": 8, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 8, + -8 + ] + ] + }, + { + "id": "row-3-action-cancel-text", + "type": "text", + "x": 1264, + "y": 390, + "width": 40, + "height": 14, + "text": "Cancel", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "empty-rows-area", + "type": "rectangle", + "x": 304, + "y": 424, + "width": 1112, + "height": 396, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-bar", + "type": "rectangle", + "x": 304, + "y": 820, + "width": 1112, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-info", + "type": "text", + "x": 328, + "y": 836, + "width": 110, + "height": 16, + "text": "Showing 1-3 of 3", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-prev", + "type": "rectangle", + "x": 1284, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-prev-icon", + "type": "line", + "x": 1297, + "y": 838, + "width": 6, + "height": 10, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 6, + 0 + ], + [ + 0, + 5 + ], + [ + 6, + 10 + ] + ] + }, + { + "id": "page-1", + "type": "rectangle", + "x": 1324, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-1-text", + "type": "text", + "x": 1336, + "y": 838, + "width": 10, + "height": 12, + "text": "1", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-next", + "type": "rectangle", + "x": 1364, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-next-icon", + "type": "line", + "x": 1374, + "y": 838, + "width": 6, + "height": 10, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 6, + 5 + ], + [ + 0, + 10 + ] + ] + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/high/data-table-members.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/high/data-table-members.excalidraw new file mode 100644 index 0000000..49c8f13 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/high/data-table-members.excalidraw @@ -0,0 +1,3099 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-switcher", + "type": "rectangle", + "x": 20, + "y": 80, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-avatar", + "type": "rectangle", + "x": 32, + "y": 88, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-name", + "type": "text", + "x": 64, + "y": 92, + "width": 100, + "height": 16, + "text": "Acme Inc", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-chevron", + "type": "line", + "x": 240, + "y": 96, + "width": 8, + "height": 5, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 4, + 5 + ], + [ + 8, + 0 + ] + ] + }, + { + "id": "sidebar-section-label", + "type": "text", + "x": 20, + "y": 140, + "width": 80, + "height": 12, + "text": "WORKSPACE", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-icon", + "type": "rectangle", + "x": 32, + "y": 170, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-label", + "type": "text", + "x": 60, + "y": 172, + "width": 80, + "height": 16, + "text": "Dashboard", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-active", + "type": "rectangle", + "x": 20, + "y": 208, + "width": 240, + "height": 40, + "strokeColor": "#f97316", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-icon", + "type": "ellipse", + "x": 32, + "y": 218, + "width": 20, + "height": 20, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-label", + "type": "text", + "x": 60, + "y": 220, + "width": 60, + "height": 16, + "text": "Members", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings", + "type": "rectangle", + "x": 20, + "y": 256, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-icon", + "type": "rectangle", + "x": 32, + "y": 266, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-label", + "type": "text", + "x": 60, + "y": 268, + "width": 60, + "height": 16, + "text": "Settings", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing", + "type": "rectangle", + "x": 20, + "y": 304, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing-icon", + "type": "rectangle", + "x": 32, + "y": 314, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing-label", + "type": "text", + "x": 60, + "y": 316, + "width": 50, + "height": 16, + "text": "Billing", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-avatar", + "type": "ellipse", + "x": 28, + "y": 856, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-name", + "type": "text", + "x": 60, + "y": 860, + "width": 80, + "height": 16, + "text": "John Doe", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "text", + "x": 304, + "y": 20, + "width": 80, + "height": 24, + "text": "Members", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invite-button", + "type": "rectangle", + "x": 1260, + "y": 16, + "width": 156, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invite-icon", + "type": "line", + "x": 1276, + "y": 26, + "width": 12, + "height": 12, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 6, + 0 + ], + [ + 6, + 12 + ] + ] + }, + { + "id": "header-invite-icon2", + "type": "line", + "x": 1276, + "y": 26, + "width": 12, + "height": 12, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 6 + ], + [ + 12, + 6 + ] + ] + }, + { + "id": "header-invite-text", + "type": "text", + "x": 1296, + "y": 24, + "width": 100, + "height": 16, + "text": "Invite Member", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tabs-container", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 1112, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-members-active", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 120, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-members-label", + "type": "text", + "x": 316, + "y": 104, + "width": 96, + "height": 16, + "text": "Members (24)", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-members-underline", + "type": "rectangle", + "x": 304, + "y": 132, + "width": 120, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-invitations", + "type": "rectangle", + "x": 424, + "y": 88, + "width": 180, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-invitations-label", + "type": "text", + "x": 436, + "y": 104, + "width": 156, + "height": 16, + "text": "Pending Invitations (3)", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "tabs-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-container", + "type": "rectangle", + "x": 304, + "y": 152, + "width": 1112, + "height": 716, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "toolbar", + "type": "rectangle", + "x": 304, + "y": 152, + "width": 1112, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-input", + "type": "rectangle", + "x": 316, + "y": 162, + "width": 260, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-icon", + "type": "ellipse", + "x": 326, + "y": 170, + "width": 16, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-icon-handle", + "type": "line", + "x": 339, + "y": 183, + "width": 6, + "height": 6, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 6, + 6 + ] + ] + }, + { + "id": "search-placeholder", + "type": "text", + "x": 350, + "y": 172, + "width": 140, + "height": 16, + "text": "Search members...", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role", + "type": "rectangle", + "x": 588, + "y": 162, + "width": 100, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role-text", + "type": "text", + "x": 600, + "y": 172, + "width": 40, + "height": 16, + "text": "Role", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role-chevron", + "type": "line", + "x": 668, + "y": 176, + "width": 8, + "height": 5, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 4, + 5 + ], + [ + 8, + 0 + ] + ] + }, + { + "id": "table-header-row", + "type": "rectangle", + "x": 304, + "y": 208, + "width": 1112, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-member-col", + "type": "text", + "x": 320, + "y": 224, + "width": 60, + "height": 14, + "text": "Member", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-role-col", + "type": "text", + "x": 600, + "y": 224, + "width": 40, + "height": 14, + "text": "Role", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-joined-col", + "type": "text", + "x": 880, + "y": 224, + "width": 50, + "height": 14, + "text": "Joined", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-actions-col", + "type": "text", + "x": 1320, + "y": 224, + "width": 55, + "height": 14, + "text": "Actions", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-1", + "type": "rectangle", + "x": 304, + "y": 256, + "width": 1112, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-avatar", + "type": "ellipse", + "x": 320, + "y": 272, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-avatar-initials", + "type": "text", + "x": 328, + "y": 280, + "width": 16, + "height": 16, + "text": "JD", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-name", + "type": "text", + "x": 364, + "y": 270, + "width": 80, + "height": 14, + "text": "John Doe", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-email", + "type": "text", + "x": 364, + "y": 288, + "width": 130, + "height": 12, + "text": "john@example.com", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-you-badge", + "type": "rectangle", + "x": 500, + "y": 276, + "width": 36, + "height": 18, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 9 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-you-text", + "type": "text", + "x": 510, + "y": 278, + "width": 18, + "height": 14, + "text": "you", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-dropdown", + "type": "rectangle", + "x": 600, + "y": 276, + "width": 100, + "height": 26, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-badge", + "type": "rectangle", + "x": 608, + "y": 280, + "width": 55, + "height": 18, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 25, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 9 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-text", + "type": "text", + "x": 616, + "y": 282, + "width": 40, + "height": 14, + "text": "Owner", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-chevron", + "type": "line", + "x": 680, + "y": 286, + "width": 8, + "height": 5, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 4, + 5 + ], + [ + 8, + 0 + ] + ] + }, + { + "id": "row-1-joined", + "type": "text", + "x": 880, + "y": 281, + "width": 90, + "height": 14, + "text": "Jan 1, 2024", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-more", + "type": "rectangle", + "x": 1356, + "y": 278, + "width": 28, + "height": 28, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-more-dot1", + "type": "ellipse", + "x": 1364, + "y": 290, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-more-dot2", + "type": "ellipse", + "x": 1370, + "y": 290, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-more-dot3", + "type": "ellipse", + "x": 1376, + "y": 290, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-2-hover", + "type": "rectangle", + "x": 304, + "y": 320, + "width": 1112, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 40, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-avatar", + "type": "ellipse", + "x": 320, + "y": 336, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-avatar-initials", + "type": "text", + "x": 329, + "y": 344, + "width": 14, + "height": 16, + "text": "JS", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-name", + "type": "text", + "x": 364, + "y": 334, + "width": 90, + "height": 14, + "text": "Jane Smith", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-email", + "type": "text", + "x": 364, + "y": 352, + "width": 130, + "height": 12, + "text": "jane@example.com", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-dropdown", + "type": "rectangle", + "x": 600, + "y": 340, + "width": 100, + "height": 26, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-badge", + "type": "rectangle", + "x": 608, + "y": 344, + "width": 55, + "height": 18, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 25, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 9 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-text", + "type": "text", + "x": 618, + "y": 346, + "width": 35, + "height": 14, + "text": "Admin", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-chevron", + "type": "line", + "x": 680, + "y": 350, + "width": 8, + "height": 5, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 4, + 5 + ], + [ + 8, + 0 + ] + ] + }, + { + "id": "row-2-joined", + "type": "text", + "x": 880, + "y": 345, + "width": 90, + "height": 14, + "text": "Jan 5, 2024", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-remove", + "type": "rectangle", + "x": 1320, + "y": 342, + "width": 28, + "height": 28, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-remove-x1", + "type": "line", + "x": 1328, + "y": 350, + "width": 12, + "height": 12, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 12, + 12 + ] + ] + }, + { + "id": "row-2-action-remove-x2", + "type": "line", + "x": 1328, + "y": 362, + "width": 12, + "height": 12, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 12, + -12 + ] + ] + }, + { + "id": "row-2-action-more", + "type": "rectangle", + "x": 1356, + "y": 342, + "width": 28, + "height": 28, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-more-dot1", + "type": "ellipse", + "x": 1364, + "y": 354, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-more-dot2", + "type": "ellipse", + "x": 1370, + "y": 354, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-more-dot3", + "type": "ellipse", + "x": 1376, + "y": 354, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-3", + "type": "rectangle", + "x": 304, + "y": 384, + "width": 1112, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-avatar", + "type": "ellipse", + "x": 320, + "y": 400, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-avatar-initials", + "type": "text", + "x": 327, + "y": 408, + "width": 18, + "height": 16, + "text": "BW", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-name", + "type": "text", + "x": 364, + "y": 398, + "width": 90, + "height": 14, + "text": "Bob Wilson", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-email", + "type": "text", + "x": 364, + "y": 416, + "width": 130, + "height": 12, + "text": "bob@example.com", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-dropdown", + "type": "rectangle", + "x": 600, + "y": 404, + "width": 100, + "height": 26, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-badge", + "type": "rectangle", + "x": 608, + "y": 408, + "width": 55, + "height": 18, + "strokeColor": "transparent", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 9 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-text", + "type": "text", + "x": 613, + "y": 410, + "width": 45, + "height": 14, + "text": "Member", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-chevron", + "type": "line", + "x": 680, + "y": 414, + "width": 8, + "height": 5, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 4, + 5 + ], + [ + 8, + 0 + ] + ] + }, + { + "id": "row-3-joined", + "type": "text", + "x": 880, + "y": 409, + "width": 95, + "height": 14, + "text": "Jan 10, 2024", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-remove", + "type": "rectangle", + "x": 1320, + "y": 406, + "width": 28, + "height": 28, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-remove-x1", + "type": "line", + "x": 1328, + "y": 414, + "width": 12, + "height": 12, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 12, + 12 + ] + ] + }, + { + "id": "row-3-action-remove-x2", + "type": "line", + "x": 1328, + "y": 426, + "width": 12, + "height": 12, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 12, + -12 + ] + ] + }, + { + "id": "row-3-action-more", + "type": "rectangle", + "x": 1356, + "y": 406, + "width": 28, + "height": 28, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-more-dot1", + "type": "ellipse", + "x": 1364, + "y": 418, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-more-dot2", + "type": "ellipse", + "x": 1370, + "y": 418, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-more-dot3", + "type": "ellipse", + "x": 1376, + "y": 418, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "empty-rows-area", + "type": "rectangle", + "x": 304, + "y": 448, + "width": 1112, + "height": 372, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-bar", + "type": "rectangle", + "x": 304, + "y": 820, + "width": 1112, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-info", + "type": "text", + "x": 328, + "y": 836, + "width": 130, + "height": 16, + "text": "Showing 1-10 of 24", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-prev", + "type": "rectangle", + "x": 1244, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-prev-icon", + "type": "line", + "x": 1257, + "y": 838, + "width": 6, + "height": 10, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 6, + 0 + ], + [ + 0, + 5 + ], + [ + 6, + 10 + ] + ] + }, + { + "id": "page-1", + "type": "rectangle", + "x": 1284, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-1-text", + "type": "text", + "x": 1296, + "y": 838, + "width": 10, + "height": 12, + "text": "1", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-2", + "type": "rectangle", + "x": 1324, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-2-text", + "type": "text", + "x": 1336, + "y": 838, + "width": 10, + "height": 12, + "text": "2", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-3", + "type": "rectangle", + "x": 1364, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-3-text", + "type": "text", + "x": 1376, + "y": 838, + "width": 10, + "height": 12, + "text": "3", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-next", + "type": "rectangle", + "x": 1404, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-next-icon", + "type": "line", + "x": 1414, + "y": 838, + "width": 6, + "height": 10, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 6, + 5 + ], + [ + 0, + 10 + ] + ] + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/high/data-table-users.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/high/data-table-users.excalidraw new file mode 100644 index 0000000..724fd6e --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/high/data-table-users.excalidraw @@ -0,0 +1,3641 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-section-label", + "type": "text", + "x": 20, + "y": 80, + "width": 80, + "height": 14, + "text": "ADMIN", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-icon", + "type": "rectangle", + "x": 32, + "y": 110, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-label", + "type": "text", + "x": 60, + "y": 112, + "width": 80, + "height": 16, + "text": "Dashboard", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-active", + "type": "rectangle", + "x": 20, + "y": 148, + "width": 240, + "height": 40, + "strokeColor": "#f97316", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-icon", + "type": "ellipse", + "x": 32, + "y": 158, + "width": 20, + "height": 20, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-label", + "type": "text", + "x": 60, + "y": 160, + "width": 50, + "height": 16, + "text": "Users", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings", + "type": "rectangle", + "x": 20, + "y": 196, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-icon", + "type": "rectangle", + "x": 32, + "y": 206, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-label", + "type": "text", + "x": 60, + "y": 208, + "width": 60, + "height": 16, + "text": "Settings", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-analytics", + "type": "rectangle", + "x": 20, + "y": 244, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-analytics-icon", + "type": "rectangle", + "x": 32, + "y": 254, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-analytics-label", + "type": "text", + "x": 60, + "y": 256, + "width": 70, + "height": 16, + "text": "Analytics", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-avatar", + "type": "ellipse", + "x": 28, + "y": 856, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-name", + "type": "text", + "x": 60, + "y": 860, + "width": 80, + "height": 16, + "text": "Admin User", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "text", + "x": 304, + "y": 20, + "width": 60, + "height": 24, + "text": "Users", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-count-badge", + "type": "rectangle", + "x": 372, + "y": 20, + "width": 80, + "height": 24, + "strokeColor": "transparent", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-count-text", + "type": "text", + "x": 382, + "y": 24, + "width": 60, + "height": 16, + "text": "1,234 users", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-container", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 1112, + "height": 780, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "toolbar", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 1112, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-input", + "type": "rectangle", + "x": 316, + "y": 98, + "width": 240, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-icon", + "type": "ellipse", + "x": 326, + "y": 106, + "width": 16, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-icon-handle", + "type": "line", + "x": 339, + "y": 119, + "width": 6, + "height": 6, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 6, + 6 + ] + ] + }, + { + "id": "search-placeholder", + "type": "text", + "x": 350, + "y": 108, + "width": 120, + "height": 16, + "text": "Search users...", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role", + "type": "rectangle", + "x": 568, + "y": 98, + "width": 100, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role-text", + "type": "text", + "x": 580, + "y": 108, + "width": 40, + "height": 16, + "text": "Role", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role-chevron", + "type": "line", + "x": 648, + "y": 112, + "width": 8, + "height": 5, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 4, + 5 + ], + [ + 8, + 0 + ] + ] + }, + { + "id": "filter-status", + "type": "rectangle", + "x": 680, + "y": 98, + "width": 100, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-status-text", + "type": "text", + "x": 692, + "y": 108, + "width": 50, + "height": 16, + "text": "Status", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-status-chevron", + "type": "line", + "x": 760, + "y": 112, + "width": 8, + "height": 5, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 4, + 5 + ], + [ + 8, + 0 + ] + ] + }, + { + "id": "filter-2fa", + "type": "rectangle", + "x": 792, + "y": 98, + "width": 80, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-2fa-text", + "type": "text", + "x": 804, + "y": 108, + "width": 30, + "height": 16, + "text": "2FA", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-2fa-chevron", + "type": "line", + "x": 852, + "y": 112, + "width": 8, + "height": 5, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 4, + 5 + ], + [ + 8, + 0 + ] + ] + }, + { + "id": "view-options", + "type": "rectangle", + "x": 1304, + "y": 98, + "width": 100, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "view-options-icon", + "type": "rectangle", + "x": 1316, + "y": 108, + "width": 14, + "height": 14, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "view-options-text", + "type": "text", + "x": 1336, + "y": 108, + "width": 60, + "height": 16, + "text": "Columns", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-header-row", + "type": "rectangle", + "x": 304, + "y": 144, + "width": 1112, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-checkbox", + "type": "rectangle", + "x": 320, + "y": 158, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-user-col", + "type": "text", + "x": 360, + "y": 160, + "width": 40, + "height": 14, + "text": "User", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-role-col", + "type": "text", + "x": 580, + "y": 160, + "width": 40, + "height": 14, + "text": "Role", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-2fa-col", + "type": "text", + "x": 720, + "y": 160, + "width": 30, + "height": 14, + "text": "2FA", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-status-col", + "type": "text", + "x": 840, + "y": 160, + "width": 50, + "height": 14, + "text": "Status", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-created-col", + "type": "text", + "x": 980, + "y": 160, + "width": 60, + "height": 14, + "text": "Created", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-actions-col", + "type": "text", + "x": 1320, + "y": 160, + "width": 55, + "height": 14, + "text": "Actions", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-1", + "type": "rectangle", + "x": 304, + "y": 192, + "width": 1112, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-checkbox", + "type": "rectangle", + "x": 320, + "y": 214, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-avatar", + "type": "ellipse", + "x": 360, + "y": 208, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-avatar-initials", + "type": "text", + "x": 368, + "y": 216, + "width": 16, + "height": 16, + "text": "JD", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-name", + "type": "text", + "x": 404, + "y": 206, + "width": 80, + "height": 14, + "text": "John Doe", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-email", + "type": "text", + "x": 404, + "y": 224, + "width": 130, + "height": 12, + "text": "john@example.com", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-badge", + "type": "rectangle", + "x": 580, + "y": 214, + "width": 60, + "height": 22, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 25, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 11 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-text", + "type": "text", + "x": 594, + "y": 218, + "width": 35, + "height": 14, + "text": "Admin", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-2fa-icon", + "type": "ellipse", + "x": 720, + "y": 216, + "width": 16, + "height": 16, + "strokeColor": "#22c55e", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-2fa-check", + "type": "line", + "x": 724, + "y": 224, + "width": 8, + "height": 6, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 3, + 4 + ], + [ + 8, + -2 + ] + ] + }, + { + "id": "row-1-status-badge", + "type": "rectangle", + "x": 840, + "y": 214, + "width": 60, + "height": 22, + "strokeColor": "transparent", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 25, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 11 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status-dot", + "type": "ellipse", + "x": 850, + "y": 221, + "width": 8, + "height": 8, + "strokeColor": "transparent", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status-text", + "type": "text", + "x": 862, + "y": 218, + "width": 35, + "height": 14, + "text": "Active", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#22c55e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-created", + "type": "text", + "x": 980, + "y": 217, + "width": 90, + "height": 14, + "text": "Jan 15, 2024", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-edit", + "type": "rectangle", + "x": 1320, + "y": 212, + "width": 28, + "height": 28, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-edit-icon", + "type": "rectangle", + "x": 1328, + "y": 220, + "width": 12, + "height": 12, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-more", + "type": "rectangle", + "x": 1356, + "y": 212, + "width": 28, + "height": 28, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-more-dot1", + "type": "ellipse", + "x": 1364, + "y": 224, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-more-dot2", + "type": "ellipse", + "x": 1370, + "y": 224, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-more-dot3", + "type": "ellipse", + "x": 1376, + "y": 224, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-2-hover", + "type": "rectangle", + "x": 304, + "y": 256, + "width": 1112, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 40, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-checkbox", + "type": "rectangle", + "x": 320, + "y": 278, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-avatar", + "type": "ellipse", + "x": 360, + "y": 272, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-avatar-initials", + "type": "text", + "x": 369, + "y": 280, + "width": 14, + "height": 16, + "text": "JS", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-name", + "type": "text", + "x": 404, + "y": 270, + "width": 90, + "height": 14, + "text": "Jane Smith", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-email", + "type": "text", + "x": 404, + "y": 288, + "width": 130, + "height": 12, + "text": "jane@example.com", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-badge", + "type": "rectangle", + "x": 580, + "y": 278, + "width": 50, + "height": 22, + "strokeColor": "transparent", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 11 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-text", + "type": "text", + "x": 592, + "y": 282, + "width": 30, + "height": 14, + "text": "User", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-2fa-icon", + "type": "ellipse", + "x": 720, + "y": 280, + "width": 16, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-2fa-x1", + "type": "line", + "x": 724, + "y": 284, + "width": 8, + "height": 8, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 8, + 8 + ] + ] + }, + { + "id": "row-2-2fa-x2", + "type": "line", + "x": 724, + "y": 292, + "width": 8, + "height": 8, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 8, + -8 + ] + ] + }, + { + "id": "row-2-status-badge", + "type": "rectangle", + "x": 840, + "y": 278, + "width": 60, + "height": 22, + "strokeColor": "transparent", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 25, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 11 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status-dot", + "type": "ellipse", + "x": 850, + "y": 285, + "width": 8, + "height": 8, + "strokeColor": "transparent", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status-text", + "type": "text", + "x": 862, + "y": 282, + "width": 35, + "height": 14, + "text": "Active", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#22c55e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-created", + "type": "text", + "x": 980, + "y": 281, + "width": 90, + "height": 14, + "text": "Jan 12, 2024", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-edit", + "type": "rectangle", + "x": 1320, + "y": 276, + "width": 28, + "height": 28, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-edit-icon", + "type": "rectangle", + "x": 1328, + "y": 284, + "width": 12, + "height": 12, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-more", + "type": "rectangle", + "x": 1356, + "y": 276, + "width": 28, + "height": 28, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-more-dot1", + "type": "ellipse", + "x": 1364, + "y": 288, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-more-dot2", + "type": "ellipse", + "x": 1370, + "y": 288, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-more-dot3", + "type": "ellipse", + "x": 1376, + "y": 288, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-3", + "type": "rectangle", + "x": 304, + "y": 320, + "width": 1112, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-checkbox", + "type": "rectangle", + "x": 320, + "y": 342, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-avatar", + "type": "ellipse", + "x": 360, + "y": 336, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-avatar-initials", + "type": "text", + "x": 367, + "y": 344, + "width": 18, + "height": 16, + "text": "BW", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-name", + "type": "text", + "x": 404, + "y": 334, + "width": 90, + "height": 14, + "text": "Bob Wilson", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-email", + "type": "text", + "x": 404, + "y": 352, + "width": 130, + "height": 12, + "text": "bob@example.com", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-badge", + "type": "rectangle", + "x": 580, + "y": 342, + "width": 50, + "height": 22, + "strokeColor": "transparent", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 11 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-text", + "type": "text", + "x": 592, + "y": 346, + "width": 30, + "height": 14, + "text": "User", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-2fa-icon", + "type": "ellipse", + "x": 720, + "y": 344, + "width": 16, + "height": 16, + "strokeColor": "#22c55e", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-2fa-check", + "type": "line", + "x": 724, + "y": 352, + "width": 8, + "height": 6, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 3, + 4 + ], + [ + 8, + -2 + ] + ] + }, + { + "id": "row-3-status-badge", + "type": "rectangle", + "x": 840, + "y": 342, + "width": 62, + "height": 22, + "strokeColor": "transparent", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 25, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 11 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-status-dot", + "type": "ellipse", + "x": 850, + "y": 349, + "width": 8, + "height": 8, + "strokeColor": "transparent", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-status-text", + "type": "text", + "x": 862, + "y": 346, + "width": 40, + "height": 14, + "text": "Banned", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-created", + "type": "text", + "x": 980, + "y": 345, + "width": 90, + "height": 14, + "text": "Jan 10, 2024", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-edit", + "type": "rectangle", + "x": 1320, + "y": 340, + "width": 28, + "height": 28, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-edit-icon", + "type": "rectangle", + "x": 1328, + "y": 348, + "width": 12, + "height": 12, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-more", + "type": "rectangle", + "x": 1356, + "y": 340, + "width": 28, + "height": 28, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-more-dot1", + "type": "ellipse", + "x": 1364, + "y": 352, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-more-dot2", + "type": "ellipse", + "x": 1370, + "y": 352, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-more-dot3", + "type": "ellipse", + "x": 1376, + "y": 352, + "width": 4, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "empty-rows-area", + "type": "rectangle", + "x": 304, + "y": 384, + "width": 1112, + "height": 384, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-bar", + "type": "rectangle", + "x": 304, + "y": 820, + "width": 1112, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-info", + "type": "text", + "x": 328, + "y": 836, + "width": 150, + "height": 16, + "text": "Showing 1-10 of 1,234", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-first", + "type": "rectangle", + "x": 1084, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-first-icon1", + "type": "line", + "x": 1094, + "y": 838, + "width": 6, + "height": 10, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 6, + 0 + ], + [ + 0, + 5 + ], + [ + 6, + 10 + ] + ] + }, + { + "id": "page-first-icon2", + "type": "line", + "x": 1100, + "y": 838, + "width": 6, + "height": 10, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 6, + 0 + ], + [ + 0, + 5 + ], + [ + 6, + 10 + ] + ] + }, + { + "id": "page-prev", + "type": "rectangle", + "x": 1124, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-prev-icon", + "type": "line", + "x": 1137, + "y": 838, + "width": 6, + "height": 10, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 6, + 0 + ], + [ + 0, + 5 + ], + [ + 6, + 10 + ] + ] + }, + { + "id": "page-1", + "type": "rectangle", + "x": 1164, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-1-text", + "type": "text", + "x": 1176, + "y": 838, + "width": 10, + "height": 12, + "text": "1", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-2", + "type": "rectangle", + "x": 1204, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-2-text", + "type": "text", + "x": 1216, + "y": 838, + "width": 10, + "height": 12, + "text": "2", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-3", + "type": "rectangle", + "x": 1244, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-3-text", + "type": "text", + "x": 1256, + "y": 838, + "width": 10, + "height": 12, + "text": "3", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-ellipsis", + "type": "text", + "x": 1284, + "y": 838, + "width": 20, + "height": 12, + "text": "...", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-last-num", + "type": "rectangle", + "x": 1316, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-last-num-text", + "type": "text", + "x": 1322, + "y": 838, + "width": 20, + "height": 12, + "text": "124", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-next", + "type": "rectangle", + "x": 1356, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-next-icon", + "type": "line", + "x": 1366, + "y": 838, + "width": 6, + "height": 10, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 6, + 5 + ], + [ + 0, + 10 + ] + ] + }, + { + "id": "page-last", + "type": "rectangle", + "x": 1396, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-last-icon1", + "type": "line", + "x": 1404, + "y": 838, + "width": 6, + "height": 10, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 6, + 5 + ], + [ + 0, + 10 + ] + ] + }, + { + "id": "page-last-icon2", + "type": "line", + "x": 1410, + "y": 838, + "width": 6, + "height": 10, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1.5, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 6, + 5 + ], + [ + 0, + 10 + ] + ] + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/high/settings-billing.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/high/settings-billing.excalidraw new file mode 100644 index 0000000..4145486 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/high/settings-billing.excalidraw @@ -0,0 +1,2809 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-text", + "type": "text", + "x": 40, + "y": 24, + "width": 80, + "height": 16, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-icon-1", + "type": "rectangle", + "x": 32, + "y": 108, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-1", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-1-text", + "type": "text", + "x": 60, + "y": 112, + "width": 80, + "height": 16, + "text": "Dashboard", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-icon-2", + "type": "rectangle", + "x": 32, + "y": 158, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-2", + "type": "rectangle", + "x": 20, + "y": 150, + "width": 240, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-2-text", + "type": "text", + "x": 60, + "y": 162, + "width": 80, + "height": 16, + "text": "Browse MCPs", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-icon-3", + "type": "rectangle", + "x": 32, + "y": 208, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-3", + "type": "rectangle", + "x": 20, + "y": 200, + "width": 240, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-3-text", + "type": "text", + "x": 60, + "y": 212, + "width": 80, + "height": 16, + "text": "Bundles", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-icon-settings", + "type": "rectangle", + "x": 32, + "y": 268, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-active", + "type": "rectangle", + "x": 20, + "y": 260, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-text", + "type": "text", + "x": 60, + "y": 272, + "width": 80, + "height": 16, + "text": "Settings", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-avatar", + "type": "ellipse", + "x": 28, + "y": 856, + "width": 28, + "height": 28, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 848, + "width": 240, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user-text", + "type": "text", + "x": 64, + "y": 856, + "width": 100, + "height": 14, + "text": "John Doe", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-email", + "type": "text", + "x": 64, + "y": 872, + "width": 140, + "height": 12, + "text": "john@example.com", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title-text", + "type": "text", + "x": 304, + "y": 22, + "width": 100, + "height": 20, + "text": "Settings", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-breadcrumb", + "type": "text", + "x": 304, + "y": 44, + "width": 200, + "height": 12, + "text": "Dashboard / Settings / Billing", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tabs-container", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 300, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-general", + "type": "rectangle", + "x": 308, + "y": 92, + "width": 92, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-general-text", + "type": "text", + "x": 330, + "y": 100, + "width": 48, + "height": 16, + "text": "General", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "tabs-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-security", + "type": "rectangle", + "x": 404, + "y": 92, + "width": 92, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-security-text", + "type": "text", + "x": 424, + "y": 100, + "width": 56, + "height": 16, + "text": "Security", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "tabs-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-billing-active", + "type": "rectangle", + "x": 500, + "y": 92, + "width": 92, + "height": 32, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-billing-text", + "type": "text", + "x": 528, + "y": 100, + "width": 40, + "height": 16, + "text": "Billing", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-card", + "type": "rectangle", + "x": 304, + "y": 152, + "width": 560, + "height": 240, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "plan-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-card-shadow", + "type": "rectangle", + "x": 306, + "y": 154, + "width": 560, + "height": 240, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": [ + "plan-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-icon-bg", + "type": "rectangle", + "x": 328, + "y": 176, + "width": 48, + "height": 48, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 15, + "groupIds": [ + "plan-section" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-icon", + "type": "rectangle", + "x": 340, + "y": 188, + "width": 24, + "height": 24, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "plan-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-title-text", + "type": "text", + "x": 392, + "y": 176, + "width": 120, + "height": 20, + "text": "Current Plan", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "plan-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-name-badge", + "type": "rectangle", + "x": 392, + "y": 204, + "width": 52, + "height": 24, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "plan-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-name-badge-text", + "type": "text", + "x": 408, + "y": 208, + "width": 20, + "height": 16, + "text": "Pro", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "plan-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "most-popular-badge", + "type": "rectangle", + "x": 452, + "y": 204, + "width": 100, + "height": 24, + "strokeColor": "#22c55e", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 15, + "groupIds": [ + "plan-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "most-popular-badge-text", + "type": "text", + "x": 462, + "y": 208, + "width": 80, + "height": 16, + "text": "Most Popular", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#22c55e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "plan-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-price-large", + "type": "text", + "x": 328, + "y": 244, + "width": 60, + "height": 36, + "text": "$29", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "plan-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-price-period", + "type": "text", + "x": 392, + "y": 258, + "width": 50, + "height": 16, + "text": "/month", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "plan-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-feature-1-check", + "type": "ellipse", + "x": 328, + "y": 296, + "width": 16, + "height": 16, + "strokeColor": "#22c55e", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "plan-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-feature-1-text", + "type": "text", + "x": 352, + "y": 296, + "width": 200, + "height": 16, + "text": "Unlimited MCP installations", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "plan-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-feature-2-check", + "type": "ellipse", + "x": 328, + "y": 320, + "width": 16, + "height": 16, + "strokeColor": "#22c55e", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "plan-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-feature-2-text", + "type": "text", + "x": 352, + "y": 320, + "width": 180, + "height": 16, + "text": "Priority support", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "plan-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-feature-3-check", + "type": "ellipse", + "x": 328, + "y": 344, + "width": 16, + "height": 16, + "strokeColor": "#22c55e", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "plan-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-feature-3-text", + "type": "text", + "x": 352, + "y": 344, + "width": 160, + "height": 16, + "text": "API access included", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "plan-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "manage-subscription-button", + "type": "rectangle", + "x": 700, + "y": 176, + "width": 140, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "plan-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "manage-subscription-button-text", + "type": "text", + "x": 710, + "y": 190, + "width": 120, + "height": 16, + "text": "Change plan", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "plan-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-card", + "type": "rectangle", + "x": 880, + "y": 152, + "width": 280, + "height": 240, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "credits-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-card-shadow", + "type": "rectangle", + "x": 882, + "y": 154, + "width": 280, + "height": 240, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": [ + "credits-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-title-text", + "type": "text", + "x": 904, + "y": 176, + "width": 80, + "height": 20, + "text": "Credits", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "credits-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-balance-number", + "type": "text", + "x": 904, + "y": 212, + "width": 100, + "height": 36, + "text": "2,450", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "credits-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-balance-label", + "type": "text", + "x": 904, + "y": 252, + "width": 120, + "height": 14, + "text": "credits remaining", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "credits-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-usage-bar-bg", + "type": "rectangle", + "x": 904, + "y": 284, + "width": 232, + "height": 12, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 40, + "groupIds": [ + "credits-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-usage-bar-fill", + "type": "rectangle", + "x": 904, + "y": 284, + "width": 140, + "height": 12, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "credits-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-usage-text", + "type": "text", + "x": 904, + "y": 304, + "width": 200, + "height": 12, + "text": "2,450 of 5,000 credits used (49%)", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "credits-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "buy-credits-button", + "type": "rectangle", + "x": 904, + "y": 336, + "width": 140, + "height": 44, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "credits-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "buy-credits-button-text", + "type": "text", + "x": 940, + "y": 350, + "width": 68, + "height": 16, + "text": "Buy credits", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "credits-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credit-history-link", + "type": "text", + "x": 1052, + "y": 350, + "width": 100, + "height": 16, + "text": "View history", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "credits-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "payment-method-card", + "type": "rectangle", + "x": 304, + "y": 416, + "width": 560, + "height": 120, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "payment-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "payment-card-shadow", + "type": "rectangle", + "x": 306, + "y": 418, + "width": 560, + "height": 120, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": [ + "payment-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "payment-title-text", + "type": "text", + "x": 328, + "y": 440, + "width": 140, + "height": 20, + "text": "Payment Method", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "payment-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-brand-icon", + "type": "rectangle", + "x": 328, + "y": 480, + "width": 48, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "payment-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-brand-text", + "type": "text", + "x": 338, + "y": 488, + "width": 28, + "height": 16, + "text": "VISA", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "payment-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-masked-number", + "type": "text", + "x": 392, + "y": 480, + "width": 180, + "height": 16, + "text": "**** **** **** 4242", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "payment-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-expiry", + "type": "text", + "x": 392, + "y": 500, + "width": 100, + "height": 12, + "text": "Expires 12/25", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "payment-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "update-payment-button", + "type": "rectangle", + "x": 760, + "y": 476, + "width": 80, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "payment-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "update-payment-button-text", + "type": "text", + "x": 778, + "y": 488, + "width": 44, + "height": 16, + "text": "Update", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "payment-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "billing-history-card", + "type": "rectangle", + "x": 304, + "y": 560, + "width": 856, + "height": 300, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "billing-history-shadow", + "type": "rectangle", + "x": 306, + "y": 562, + "width": 856, + "height": 300, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "history-title-text", + "type": "text", + "x": 328, + "y": 584, + "width": 140, + "height": 20, + "text": "Billing History", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "view-all-invoices", + "type": "text", + "x": 1060, + "y": 586, + "width": 80, + "height": 16, + "text": "View all", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-header", + "type": "rectangle", + "x": 328, + "y": 620, + "width": 808, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-date-text", + "type": "text", + "x": 352, + "y": 632, + "width": 60, + "height": 16, + "text": "Date", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-description-text", + "type": "text", + "x": 520, + "y": 632, + "width": 80, + "height": 16, + "text": "Description", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-amount-text", + "type": "text", + "x": 780, + "y": 632, + "width": 60, + "height": 16, + "text": "Amount", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-status-text", + "type": "text", + "x": 900, + "y": 632, + "width": 50, + "height": 16, + "text": "Status", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invoice-text", + "type": "text", + "x": 1020, + "y": 632, + "width": 60, + "height": 16, + "text": "Invoice", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1", + "type": "rectangle", + "x": 328, + "y": 660, + "width": 808, + "height": 52, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-date-text", + "type": "text", + "x": 352, + "y": 678, + "width": 100, + "height": 16, + "text": "Jan 1, 2026", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-desc-text", + "type": "text", + "x": 520, + "y": 678, + "width": 180, + "height": 16, + "text": "Pro Plan - Monthly", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-amount-text", + "type": "text", + "x": 780, + "y": 678, + "width": 60, + "height": 16, + "text": "$29.00", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status", + "type": "rectangle", + "x": 900, + "y": 672, + "width": 52, + "height": 24, + "strokeColor": "#22c55e", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 15, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status-text", + "type": "text", + "x": 912, + "y": 676, + "width": 28, + "height": 16, + "text": "Paid", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#22c55e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-pdf-icon", + "type": "rectangle", + "x": 1020, + "y": 672, + "width": 24, + "height": 24, + "strokeColor": "#ef4444", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 20, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-pdf-text", + "type": "text", + "x": 1024, + "y": 676, + "width": 16, + "height": 16, + "text": "PDF", + "fontSize": 8, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-download-text", + "type": "text", + "x": 1052, + "y": 678, + "width": 60, + "height": 16, + "text": "Download", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2", + "type": "rectangle", + "x": 328, + "y": 712, + "width": 808, + "height": 52, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-date-text", + "type": "text", + "x": 352, + "y": 730, + "width": 100, + "height": 16, + "text": "Dec 1, 2025", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-desc-text", + "type": "text", + "x": 520, + "y": 730, + "width": 180, + "height": 16, + "text": "Pro Plan - Monthly", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-amount-text", + "type": "text", + "x": 780, + "y": 730, + "width": 60, + "height": 16, + "text": "$29.00", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status", + "type": "rectangle", + "x": 900, + "y": 724, + "width": 52, + "height": 24, + "strokeColor": "#22c55e", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 15, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status-text", + "type": "text", + "x": 912, + "y": 728, + "width": 28, + "height": 16, + "text": "Paid", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#22c55e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-pdf-icon", + "type": "rectangle", + "x": 1020, + "y": 724, + "width": 24, + "height": 24, + "strokeColor": "#ef4444", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 20, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-pdf-text", + "type": "text", + "x": 1024, + "y": 728, + "width": 16, + "height": 16, + "text": "PDF", + "fontSize": 8, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-download-text", + "type": "text", + "x": 1052, + "y": 730, + "width": 60, + "height": 16, + "text": "Download", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3", + "type": "rectangle", + "x": 328, + "y": 764, + "width": 808, + "height": 52, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-date-text", + "type": "text", + "x": 352, + "y": 782, + "width": 100, + "height": 16, + "text": "Nov 1, 2025", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-desc-text", + "type": "text", + "x": 520, + "y": 782, + "width": 180, + "height": 16, + "text": "Credit Purchase", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-amount-text", + "type": "text", + "x": 780, + "y": 782, + "width": 60, + "height": 16, + "text": "$49.00", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-status", + "type": "rectangle", + "x": 896, + "y": 776, + "width": 64, + "height": 24, + "strokeColor": "$warning", + "backgroundColor": "$warning", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 15, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-status-text", + "type": "text", + "x": 904, + "y": 780, + "width": 48, + "height": 16, + "text": "Pending", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "$warning", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-pdf-icon", + "type": "rectangle", + "x": 1020, + "y": 776, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 40, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-pending-text", + "type": "text", + "x": 1052, + "y": 782, + "width": 60, + "height": 16, + "text": "Pending", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/high/settings-general.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/high/settings-general.excalidraw new file mode 100644 index 0000000..54a9cc5 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/high/settings-general.excalidraw @@ -0,0 +1,1910 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-text", + "type": "text", + "x": 40, + "y": 24, + "width": 80, + "height": 16, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-icon-1", + "type": "rectangle", + "x": 32, + "y": 108, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-1", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-1-text", + "type": "text", + "x": 60, + "y": 112, + "width": 80, + "height": 16, + "text": "Dashboard", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-icon-2", + "type": "rectangle", + "x": 32, + "y": 158, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-2", + "type": "rectangle", + "x": 20, + "y": 150, + "width": 240, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-2-text", + "type": "text", + "x": 60, + "y": 162, + "width": 80, + "height": 16, + "text": "Browse MCPs", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-icon-3", + "type": "rectangle", + "x": 32, + "y": 208, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-3", + "type": "rectangle", + "x": 20, + "y": 200, + "width": 240, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-3-text", + "type": "text", + "x": 60, + "y": 212, + "width": 80, + "height": 16, + "text": "Bundles", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-icon-settings", + "type": "rectangle", + "x": 32, + "y": 268, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-active", + "type": "rectangle", + "x": 20, + "y": 260, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-text", + "type": "text", + "x": 60, + "y": 272, + "width": 80, + "height": 16, + "text": "Settings", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-avatar", + "type": "ellipse", + "x": 28, + "y": 856, + "width": 28, + "height": 28, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 848, + "width": 240, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user-text", + "type": "text", + "x": 64, + "y": 856, + "width": 100, + "height": 14, + "text": "John Doe", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-email", + "type": "text", + "x": 64, + "y": 872, + "width": 140, + "height": 12, + "text": "john@example.com", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title-text", + "type": "text", + "x": 304, + "y": 22, + "width": 100, + "height": 20, + "text": "Settings", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-breadcrumb", + "type": "text", + "x": 304, + "y": 44, + "width": 200, + "height": 12, + "text": "Dashboard / Settings / General", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tabs-container", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 300, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-general-active", + "type": "rectangle", + "x": 308, + "y": 92, + "width": 92, + "height": 32, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-general-text", + "type": "text", + "x": 330, + "y": 100, + "width": 48, + "height": 16, + "text": "General", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-security", + "type": "rectangle", + "x": 404, + "y": 92, + "width": 92, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-security-text", + "type": "text", + "x": 424, + "y": 100, + "width": 56, + "height": 16, + "text": "Security", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "tabs-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-billing", + "type": "rectangle", + "x": 500, + "y": 92, + "width": 92, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-billing-text", + "type": "text", + "x": 528, + "y": 100, + "width": 40, + "height": 16, + "text": "Billing", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "tabs-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "profile-card", + "type": "rectangle", + "x": 304, + "y": 152, + "width": 720, + "height": 320, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "profile-card-shadow", + "type": "rectangle", + "x": 306, + "y": 154, + "width": 720, + "height": 320, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": [ + "profile-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "profile-card-title-text", + "type": "text", + "x": 328, + "y": 176, + "width": 100, + "height": 20, + "text": "Profile", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "profile-card-subtitle", + "type": "text", + "x": 328, + "y": 200, + "width": 300, + "height": 14, + "text": "Manage your personal information and preferences", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "profile-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "avatar-circle", + "type": "ellipse", + "x": 328, + "y": 232, + "width": 80, + "height": 80, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "avatar-initials", + "type": "text", + "x": 348, + "y": 262, + "width": 40, + "height": 20, + "text": "JD", + "fontSize": 22, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "profile-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "avatar-camera-overlay", + "type": "ellipse", + "x": 380, + "y": 284, + "width": 28, + "height": 28, + "strokeColor": "#ffffff", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "avatar-camera-icon", + "type": "rectangle", + "x": 388, + "y": 292, + "width": 12, + "height": 12, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "avatar-change-button", + "type": "rectangle", + "x": 328, + "y": 328, + "width": 100, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "avatar-change-button-text", + "type": "text", + "x": 338, + "y": 338, + "width": 80, + "height": 16, + "text": "Change photo", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-label-text", + "type": "text", + "x": 440, + "y": 232, + "width": 60, + "height": 14, + "text": "Full name", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-input", + "type": "rectangle", + "x": 440, + "y": 252, + "width": 560, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-input-text", + "type": "text", + "x": 456, + "y": 266, + "width": 80, + "height": 16, + "text": "John Doe", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-label-text", + "type": "text", + "x": 440, + "y": 312, + "width": 60, + "height": 14, + "text": "Email address", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-input", + "type": "rectangle", + "x": 440, + "y": 332, + "width": 460, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-input-text", + "type": "text", + "x": 456, + "y": 346, + "width": 140, + "height": 16, + "text": "john@example.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "verified-badge", + "type": "rectangle", + "x": 912, + "y": 340, + "width": 88, + "height": 28, + "strokeColor": "#22c55e", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 15, + "groupIds": [ + "profile-section" + ], + "roundness": { + "type": 3, + "value": 14 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "verified-badge-border", + "type": "rectangle", + "x": 912, + "y": 340, + "width": 88, + "height": 28, + "strokeColor": "#22c55e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": { + "type": 3, + "value": 14 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "verified-checkmark", + "type": "ellipse", + "x": 920, + "y": 346, + "width": 16, + "height": 16, + "strokeColor": "#22c55e", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "verified-badge-text", + "type": "text", + "x": 940, + "y": 346, + "width": 52, + "height": 16, + "text": "Verified", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#22c55e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "save-profile-button", + "type": "rectangle", + "x": 328, + "y": 400, + "width": 672, + "height": 44, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "save-profile-button-text", + "type": "text", + "x": 624, + "y": 414, + "width": 80, + "height": 16, + "text": "Save changes", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-card", + "type": "rectangle", + "x": 304, + "y": 496, + "width": 720, + "height": 160, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "language-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-card-shadow", + "type": "rectangle", + "x": 306, + "y": 498, + "width": 720, + "height": 160, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": [ + "language-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-card-title-text", + "type": "text", + "x": 328, + "y": 520, + "width": 120, + "height": 20, + "text": "Language & Region", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "language-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-card-subtitle", + "type": "text", + "x": 328, + "y": 544, + "width": 280, + "height": 14, + "text": "Set your preferred display language and region", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "language-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-label-text", + "type": "text", + "x": 328, + "y": 576, + "width": 120, + "height": 14, + "text": "Display language", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "language-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-dropdown", + "type": "rectangle", + "x": 328, + "y": 596, + "width": 320, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "language-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-flag-icon", + "type": "rectangle", + "x": 344, + "y": 608, + "width": 24, + "height": 18, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "language-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-dropdown-text", + "type": "text", + "x": 380, + "y": 610, + "width": 100, + "height": 16, + "text": "English (US)", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "language-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "dropdown-chevron", + "type": "rectangle", + "x": 620, + "y": 612, + "width": 16, + "height": 12, + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "language-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "danger-zone-card", + "type": "rectangle", + "x": 304, + "y": 680, + "width": 720, + "height": 180, + "strokeColor": "#ef4444", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "strokeStyle": "dashed", + "roughness": 0, + "opacity": 100, + "groupIds": [ + "danger-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "danger-zone-header", + "type": "rectangle", + "x": 328, + "y": 704, + "width": 672, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "danger-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "danger-zone-warning-icon", + "type": "rectangle", + "x": 328, + "y": 708, + "width": 24, + "height": 24, + "strokeColor": "#ef4444", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 20, + "groupIds": [ + "danger-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "danger-zone-warning-icon-inner", + "type": "text", + "x": 335, + "y": 710, + "width": 10, + "height": 20, + "text": "!", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "danger-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "danger-zone-title-text", + "type": "text", + "x": 360, + "y": 708, + "width": 120, + "height": 20, + "text": "Danger Zone", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "danger-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "danger-zone-description-text", + "type": "text", + "x": 328, + "y": 752, + "width": 500, + "height": 16, + "text": "Once you delete your account, there is no going back. Please be certain.", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "danger-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "danger-zone-description-text-2", + "type": "text", + "x": 328, + "y": 772, + "width": 500, + "height": 16, + "text": "This action will permanently delete your account and all associated data.", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "danger-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "delete-account-button", + "type": "rectangle", + "x": 328, + "y": 808, + "width": 180, + "height": 44, + "strokeColor": "#ef4444", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "danger-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "delete-account-button-icon", + "type": "rectangle", + "x": 352, + "y": 820, + "width": 18, + "height": 18, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "danger-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "delete-account-button-text", + "type": "text", + "x": 378, + "y": 822, + "width": 108, + "height": 16, + "text": "Delete Account", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "danger-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/high/settings-security.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/high/settings-security.excalidraw new file mode 100644 index 0000000..5a265ae --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/high/settings-security.excalidraw @@ -0,0 +1,2228 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-text", + "type": "text", + "x": 40, + "y": 24, + "width": 80, + "height": 16, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-icon-1", + "type": "rectangle", + "x": 32, + "y": 108, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-1", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-1-text", + "type": "text", + "x": 60, + "y": 112, + "width": 80, + "height": 16, + "text": "Dashboard", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-icon-2", + "type": "rectangle", + "x": 32, + "y": 158, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-2", + "type": "rectangle", + "x": 20, + "y": 150, + "width": 240, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-2-text", + "type": "text", + "x": 60, + "y": 162, + "width": 80, + "height": 16, + "text": "Browse MCPs", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-icon-3", + "type": "rectangle", + "x": 32, + "y": 208, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-3", + "type": "rectangle", + "x": 20, + "y": 200, + "width": 240, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-3-text", + "type": "text", + "x": 60, + "y": 212, + "width": 80, + "height": 16, + "text": "Bundles", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-icon-settings", + "type": "rectangle", + "x": 32, + "y": 268, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-active", + "type": "rectangle", + "x": 20, + "y": 260, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-text", + "type": "text", + "x": 60, + "y": 272, + "width": 80, + "height": 16, + "text": "Settings", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-avatar", + "type": "ellipse", + "x": 28, + "y": 856, + "width": 28, + "height": 28, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 848, + "width": 240, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user-text", + "type": "text", + "x": 64, + "y": 856, + "width": 100, + "height": 14, + "text": "John Doe", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-email", + "type": "text", + "x": 64, + "y": 872, + "width": 140, + "height": 12, + "text": "john@example.com", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title-text", + "type": "text", + "x": 304, + "y": 22, + "width": 100, + "height": 20, + "text": "Settings", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-breadcrumb", + "type": "text", + "x": 304, + "y": 44, + "width": 200, + "height": 12, + "text": "Dashboard / Settings / Security", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tabs-container", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 300, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-general", + "type": "rectangle", + "x": 308, + "y": 92, + "width": 92, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-general-text", + "type": "text", + "x": 330, + "y": 100, + "width": 48, + "height": 16, + "text": "General", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "tabs-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-security-active", + "type": "rectangle", + "x": 404, + "y": 92, + "width": 92, + "height": 32, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-security-text", + "type": "text", + "x": 424, + "y": 100, + "width": 56, + "height": 16, + "text": "Security", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-billing", + "type": "rectangle", + "x": 500, + "y": 92, + "width": 92, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-billing-text", + "type": "text", + "x": 528, + "y": 100, + "width": 40, + "height": 16, + "text": "Billing", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "tabs-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-card", + "type": "rectangle", + "x": 304, + "y": 152, + "width": 720, + "height": 160, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "twofa-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-card-shadow", + "type": "rectangle", + "x": 306, + "y": 154, + "width": 720, + "height": 160, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": [ + "twofa-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-shield-icon-bg", + "type": "rectangle", + "x": 328, + "y": 176, + "width": 40, + "height": 40, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 15, + "groupIds": [ + "twofa-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-shield-icon", + "type": "rectangle", + "x": 338, + "y": 186, + "width": 20, + "height": 20, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "twofa-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-title-text", + "type": "text", + "x": 384, + "y": 180, + "width": 200, + "height": 20, + "text": "Two-Factor Authentication", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "twofa-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-status-indicator", + "type": "rectangle", + "x": 384, + "y": 208, + "width": 80, + "height": 24, + "strokeColor": "#22c55e", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 15, + "groupIds": [ + "twofa-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-status-dot", + "type": "ellipse", + "x": 392, + "y": 216, + "width": 8, + "height": 8, + "strokeColor": "#22c55e", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "twofa-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-status-text", + "type": "text", + "x": 406, + "y": 212, + "width": 48, + "height": 16, + "text": "Enabled", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#22c55e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "twofa-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-description-text", + "type": "text", + "x": 328, + "y": 248, + "width": 500, + "height": 16, + "text": "Add an extra layer of security to your account with two-factor authentication.", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "twofa-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-recovery-hint", + "type": "text", + "x": 328, + "y": 268, + "width": 300, + "height": 14, + "text": "Recovery codes available in your backup settings.", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "twofa-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-toggle-track", + "type": "rectangle", + "x": 952, + "y": 188, + "width": 48, + "height": 28, + "strokeColor": "#22c55e", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "twofa-section" + ], + "roundness": { + "type": 3, + "value": 14 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-toggle-thumb", + "type": "ellipse", + "x": 976, + "y": 192, + "width": 20, + "height": 20, + "strokeColor": "#ffffff", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "twofa-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkeys-card", + "type": "rectangle", + "x": 304, + "y": 336, + "width": 720, + "height": 240, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "passkeys-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkeys-card-shadow", + "type": "rectangle", + "x": 306, + "y": 338, + "width": 720, + "height": 240, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": [ + "passkeys-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkeys-fingerprint-bg", + "type": "rectangle", + "x": 328, + "y": 360, + "width": 40, + "height": 40, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 15, + "groupIds": [ + "passkeys-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkeys-fingerprint-icon", + "type": "ellipse", + "x": 338, + "y": 370, + "width": 20, + "height": 20, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "passkeys-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkeys-title-text", + "type": "text", + "x": 384, + "y": 364, + "width": 100, + "height": 20, + "text": "Passkeys", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "passkeys-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkeys-subtitle", + "type": "text", + "x": 384, + "y": 388, + "width": 300, + "height": 14, + "text": "Use biometrics or security keys to sign in", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "passkeys-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-item-1", + "type": "rectangle", + "x": 328, + "y": 420, + "width": 672, + "height": 52, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "passkeys-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-1-laptop-icon", + "type": "rectangle", + "x": 344, + "y": 434, + "width": 24, + "height": 18, + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "passkeys-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-1-laptop-base", + "type": "rectangle", + "x": 340, + "y": 452, + "width": 32, + "height": 4, + "strokeColor": "#737373", + "backgroundColor": "#737373", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "passkeys-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-1-name-text", + "type": "text", + "x": 388, + "y": 434, + "width": 120, + "height": 16, + "text": "MacBook Pro", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "passkeys-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-1-date-text", + "type": "text", + "x": 388, + "y": 452, + "width": 160, + "height": 12, + "text": "Last used: 2 hours ago", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "passkeys-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-1-delete", + "type": "rectangle", + "x": 928, + "y": 432, + "width": 60, + "height": 28, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "passkeys-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-1-delete-text", + "type": "text", + "x": 940, + "y": 438, + "width": 36, + "height": 16, + "text": "Delete", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "passkeys-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-item-2", + "type": "rectangle", + "x": 328, + "y": 480, + "width": 672, + "height": 52, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "passkeys-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-2-phone-icon", + "type": "rectangle", + "x": 348, + "y": 492, + "width": 16, + "height": 24, + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "passkeys-section" + ], + "roundness": { + "type": 3, + "value": 3 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-2-name-text", + "type": "text", + "x": 388, + "y": 494, + "width": 140, + "height": 16, + "text": "iPhone 15 Pro", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "passkeys-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-2-date-text", + "type": "text", + "x": 388, + "y": 512, + "width": 160, + "height": 12, + "text": "Last used: Yesterday", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "passkeys-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-2-delete", + "type": "rectangle", + "x": 928, + "y": 492, + "width": 60, + "height": 28, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "passkeys-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-2-delete-text", + "type": "text", + "x": 940, + "y": 498, + "width": 36, + "height": 16, + "text": "Delete", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "passkeys-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "add-passkey-button", + "type": "rectangle", + "x": 328, + "y": 544, + "width": 140, + "height": 44, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "passkeys-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "add-passkey-button-text", + "type": "text", + "x": 360, + "y": 558, + "width": 76, + "height": 16, + "text": "Add passkey", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "passkeys-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sessions-card", + "type": "rectangle", + "x": 304, + "y": 600, + "width": 720, + "height": 270, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sessions-card-shadow", + "type": "rectangle", + "x": 306, + "y": 602, + "width": 720, + "height": 270, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 8, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sessions-title-text", + "type": "text", + "x": 328, + "y": 624, + "width": 140, + "height": 20, + "text": "Active Sessions", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sessions-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sessions-subtitle", + "type": "text", + "x": 328, + "y": 648, + "width": 400, + "height": 14, + "text": "Manage devices and locations where you're signed in", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sessions-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-item-1", + "type": "rectangle", + "x": 328, + "y": 680, + "width": 672, + "height": 60, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-device-icon", + "type": "rectangle", + "x": 344, + "y": 696, + "width": 24, + "height": 18, + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-browser-icon", + "type": "ellipse", + "x": 350, + "y": 700, + "width": 12, + "height": 12, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sessions-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-name-text", + "type": "text", + "x": 388, + "y": 692, + "width": 160, + "height": 16, + "text": "Chrome on macOS", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sessions-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-location-icon", + "type": "ellipse", + "x": 388, + "y": 714, + "width": 8, + "height": 8, + "strokeColor": "#737373", + "backgroundColor": "#737373", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 40, + "groupIds": [ + "sessions-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-location-text", + "type": "text", + "x": 402, + "y": 712, + "width": 200, + "height": 12, + "text": "San Francisco, CA - 192.168.x.x", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "sessions-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-current-dot", + "type": "ellipse", + "x": 916, + "y": 702, + "width": 10, + "height": 10, + "strokeColor": "#22c55e", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sessions-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-current-badge", + "type": "rectangle", + "x": 932, + "y": 696, + "width": 60, + "height": 24, + "strokeColor": "#22c55e", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 15, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-current-badge-text", + "type": "text", + "x": 942, + "y": 700, + "width": 44, + "height": 16, + "text": "Current", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#22c55e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sessions-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-item-2", + "type": "rectangle", + "x": 328, + "y": 752, + "width": 672, + "height": 60, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-phone-icon", + "type": "rectangle", + "x": 348, + "y": 768, + "width": 16, + "height": 24, + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 3 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-name-text", + "type": "text", + "x": 388, + "y": 764, + "width": 140, + "height": 16, + "text": "Safari on iPhone", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sessions-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-location-icon", + "type": "ellipse", + "x": 388, + "y": 786, + "width": 8, + "height": 8, + "strokeColor": "#737373", + "backgroundColor": "#737373", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 40, + "groupIds": [ + "sessions-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-location-text", + "type": "text", + "x": 402, + "y": 784, + "width": 200, + "height": 12, + "text": "San Francisco, CA - 192.168.x.x", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "sessions-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-revoke", + "type": "rectangle", + "x": 928, + "y": 768, + "width": 60, + "height": 28, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-revoke-text", + "type": "text", + "x": 938, + "y": 774, + "width": 44, + "height": 16, + "text": "Revoke", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sessions-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "revoke-all-link", + "type": "text", + "x": 328, + "y": 828, + "width": 180, + "height": 14, + "text": "Revoke all other sessions", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sessions-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/high/sidebar-admin.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/high/sidebar-admin.excalidraw new file mode 100644 index 0000000..f15c663 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/high/sidebar-admin.excalidraw @@ -0,0 +1,1333 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "sidebar-container", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-area", + "type": "rectangle", + "x": 12, + "y": 16, + "width": 256, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-hexagon-outer", + "type": "diamond", + "x": 24, + "y": 22, + "width": 36, + "height": 36, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": { + "type": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-hexagon-inner", + "type": "diamond", + "x": 32, + "y": 30, + "width": 20, + "height": 20, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": { + "type": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 68, + "y": 30, + "width": 80, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "MCPGet", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 14 + }, + { + "id": "admin-badge-bg", + "type": "rectangle", + "x": 158, + "y": 28, + "width": 56, + "height": 24, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "admin-badge-text", + "type": "text", + "x": 168, + "y": 32, + "width": 36, + "height": 16, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Admin", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 10 + }, + { + "id": "section-divider-1", + "type": "line", + "x": 12, + "y": 76, + "width": 256, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 256, + 0 + ] + ] + }, + { + "id": "section-label-admin-text", + "type": "text", + "x": 24, + "y": 88, + "width": 50, + "height": 14, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "section-admin" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "ADMIN", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "nav-home-active", + "type": "rectangle", + "x": 12, + "y": 108, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-home-icon-roof", + "type": "line", + "x": 24, + "y": 118, + "width": 20, + "height": 10, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 10 + ], + [ + 10, + 0 + ], + [ + 20, + 10 + ] + ] + }, + { + "id": "nav-home-icon-body", + "type": "rectangle", + "x": 27, + "y": 126, + "width": 14, + "height": 12, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-home-icon-door", + "type": "rectangle", + "x": 31, + "y": 130, + "width": 6, + "height": 8, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-home-text", + "type": "text", + "x": 56, + "y": 118, + "width": 60, + "height": 20, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Home", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-users-hover", + "type": "rectangle", + "x": 12, + "y": 156, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-users-icon-person1", + "type": "ellipse", + "x": 24, + "y": 164, + "width": 10, + "height": 10, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-users-icon-body1", + "type": "line", + "x": 21, + "y": 176, + "width": 16, + "height": 10, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": { + "type": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 10 + ], + [ + 8, + 0 + ], + [ + 16, + 10 + ] + ] + }, + { + "id": "nav-users-icon-person2", + "type": "ellipse", + "x": 34, + "y": 166, + "width": 8, + "height": 8, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-users-icon-body2", + "type": "line", + "x": 32, + "y": 176, + "width": 12, + "height": 8, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": { + "type": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 8 + ], + [ + 6, + 0 + ], + [ + 12, + 8 + ] + ] + }, + { + "id": "nav-users-text", + "type": "text", + "x": 56, + "y": 166, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Users", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-users-badge-bg", + "type": "rectangle", + "x": 216, + "y": 168, + "width": 40, + "height": 18, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": { + "type": 3, + "value": 9 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-users-badge-text", + "type": "text", + "x": 224, + "y": 170, + "width": 24, + "height": 14, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "1.2k", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "nav-orgs", + "type": "rectangle", + "x": 12, + "y": 204, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-orgs-icon-building-main", + "type": "rectangle", + "x": 26, + "y": 214, + "width": 16, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-orgs-icon-window1", + "type": "rectangle", + "x": 29, + "y": 218, + "width": 4, + "height": 4, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-orgs-icon-window2", + "type": "rectangle", + "x": 35, + "y": 218, + "width": 4, + "height": 4, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-orgs-icon-window3", + "type": "rectangle", + "x": 29, + "y": 225, + "width": 4, + "height": 4, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-orgs-icon-window4", + "type": "rectangle", + "x": 35, + "y": 225, + "width": 4, + "height": 4, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-orgs-text", + "type": "text", + "x": 56, + "y": 214, + "width": 100, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Organizations", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-customers", + "type": "rectangle", + "x": 12, + "y": 252, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-customers-icon-hand", + "type": "line", + "x": 24, + "y": 270, + "width": 18, + "height": 12, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": { + "type": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 12 + ], + [ + 9, + 0 + ], + [ + 18, + 12 + ] + ] + }, + { + "id": "nav-customers-icon-coin", + "type": "ellipse", + "x": 28, + "y": 262, + "width": 12, + "height": 12, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-customers-icon-dollar", + "type": "text", + "x": 31, + "y": 263, + "width": 6, + "height": 10, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "$", + "fontSize": 9, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 7 + }, + { + "id": "nav-customers-text", + "type": "text", + "x": 56, + "y": 262, + "width": 80, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Customers", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-2", + "type": "line", + "x": 12, + "y": 308, + "width": 256, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 256, + 0 + ] + ] + }, + { + "id": "nav-back", + "type": "rectangle", + "x": 12, + "y": 320, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-back" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-back-arrow-line", + "type": "line", + "x": 24, + "y": 340, + "width": 16, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-back" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 16, + 0 + ] + ] + }, + { + "id": "nav-back-arrow-head", + "type": "line", + "x": 24, + "y": 334, + "width": 6, + "height": 12, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-back" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 6, + 0 + ], + [ + 0, + 6 + ], + [ + 6, + 12 + ] + ] + }, + { + "id": "nav-back-text", + "type": "text", + "x": 56, + "y": 330, + "width": 140, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-back" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Back to Dashboard", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-footer", + "type": "line", + "x": 12, + "y": 812, + "width": 256, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 256, + 0 + ] + ] + }, + { + "id": "user-footer", + "type": "rectangle", + "x": 12, + "y": 824, + "width": 256, + "height": 64, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-avatar", + "type": "ellipse", + "x": 24, + "y": 836, + "width": 40, + "height": 40, + "strokeColor": "#ef4444", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-avatar-initials", + "type": "text", + "x": 32, + "y": 848, + "width": 24, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "AU", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 10 + }, + { + "id": "user-name", + "type": "text", + "x": 76, + "y": 838, + "width": 100, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Admin User", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 12 + }, + { + "id": "role-badge-bg", + "type": "rectangle", + "x": 76, + "y": 858, + "width": 50, + "height": 18, + "strokeColor": "transparent", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": { + "type": 3, + "value": 9 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "role-badge-text", + "type": "text", + "x": 84, + "y": 860, + "width": 34, + "height": 14, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "admin", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "dropdown-indicator", + "type": "line", + "x": 240, + "y": 850, + "width": 12, + "height": 8, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 6, + 8 + ], + [ + 12, + 0 + ] + ] + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/high/sidebar-apps.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/high/sidebar-apps.excalidraw new file mode 100644 index 0000000..5c917bb --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/high/sidebar-apps.excalidraw @@ -0,0 +1,2185 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "sidebar-container", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-area", + "type": "rectangle", + "x": 12, + "y": 16, + "width": 256, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-hexagon-outer", + "type": "diamond", + "x": 24, + "y": 22, + "width": 36, + "height": 36, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": { + "type": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-hexagon-inner", + "type": "diamond", + "x": 32, + "y": 30, + "width": 20, + "height": 20, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": { + "type": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 68, + "y": 30, + "width": 80, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "MCPGet", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 14 + }, + { + "id": "collapse-button", + "type": "rectangle", + "x": 236, + "y": 24, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "collapse-chevron", + "type": "line", + "x": 248, + "y": 35, + "width": 8, + "height": 10, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 8, + 0 + ], + [ + 0, + 5 + ], + [ + 8, + 10 + ] + ] + }, + { + "id": "section-divider-1", + "type": "line", + "x": 12, + "y": 76, + "width": 256, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 256, + 0 + ] + ] + }, + { + "id": "section-label-apps-text", + "type": "text", + "x": 24, + "y": 88, + "width": 40, + "height": 14, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "section-apps" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "APPS", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "nav-chat-active", + "type": "rectangle", + "x": 12, + "y": 108, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-chat-icon-bubble", + "type": "rectangle", + "x": 24, + "y": 118, + "width": 20, + "height": 16, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-chat-icon-tail", + "type": "line", + "x": 24, + "y": 130, + "width": 6, + "height": 6, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 6 + ], + [ + 6, + 0 + ] + ] + }, + { + "id": "nav-chat-text", + "type": "text", + "x": 56, + "y": 118, + "width": 60, + "height": 20, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Chat", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-chat-badge-bg", + "type": "rectangle", + "x": 228, + "y": 118, + "width": 28, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-chat-badge-text", + "type": "text", + "x": 236, + "y": 120, + "width": 12, + "height": 16, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "3", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 10 + }, + { + "id": "nav-image-hover", + "type": "rectangle", + "x": 12, + "y": 156, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-image-icon-frame", + "type": "rectangle", + "x": 24, + "y": 166, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-image-icon-mountain", + "type": "line", + "x": 27, + "y": 180, + "width": 14, + "height": 8, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 8 + ], + [ + 5, + 0 + ], + [ + 9, + 4 + ], + [ + 14, + 8 + ] + ] + }, + { + "id": "nav-image-icon-sun", + "type": "ellipse", + "x": 36, + "y": 169, + "width": 5, + "height": 5, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-image-text", + "type": "text", + "x": 56, + "y": 166, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Image", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-tts", + "type": "rectangle", + "x": 12, + "y": 204, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-tts-icon-speaker", + "type": "rectangle", + "x": 24, + "y": 218, + "width": 8, + "height": 12, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": { + "type": 3, + "value": 1 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-tts-icon-wave1", + "type": "line", + "x": 34, + "y": 216, + "width": 0, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": { + "type": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 4 + ], + [ + 4, + 0 + ], + [ + 4, + 8 + ], + [ + 0, + 12 + ] + ] + }, + { + "id": "nav-tts-icon-wave2", + "type": "line", + "x": 40, + "y": 214, + "width": 0, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": { + "type": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 4 + ], + [ + 4, + 0 + ], + [ + 4, + 10 + ], + [ + 0, + 16 + ] + ] + }, + { + "id": "nav-tts-text", + "type": "text", + "x": 56, + "y": 214, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "TTS", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-pdf", + "type": "rectangle", + "x": 12, + "y": 252, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-pdf-icon-doc", + "type": "rectangle", + "x": 24, + "y": 262, + "width": 16, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-pdf-icon-fold", + "type": "line", + "x": 34, + "y": 262, + "width": 6, + "height": 6, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 6 + ], + [ + 0, + 0 + ], + [ + 6, + 0 + ] + ] + }, + { + "id": "nav-pdf-icon-line1", + "type": "line", + "x": 27, + "y": 272, + "width": 10, + "height": 0, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 10, + 0 + ] + ] + }, + { + "id": "nav-pdf-icon-line2", + "type": "line", + "x": 27, + "y": 276, + "width": 10, + "height": 0, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 10, + 0 + ] + ] + }, + { + "id": "nav-pdf-text", + "type": "text", + "x": 56, + "y": 262, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "PDF", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-agent", + "type": "rectangle", + "x": 12, + "y": 300, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-agent-icon-sparkle-main", + "type": "diamond", + "x": 28, + "y": 314, + "width": 12, + "height": 12, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-agent-icon-sparkle-small", + "type": "diamond", + "x": 38, + "y": 310, + "width": 6, + "height": 6, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-agent-text", + "type": "text", + "x": 56, + "y": 310, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Agent", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-2", + "type": "line", + "x": 12, + "y": 356, + "width": 256, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 256, + 0 + ] + ] + }, + { + "id": "section-label-free-tools-text", + "type": "text", + "x": 24, + "y": 368, + "width": 80, + "height": 14, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "section-free-tools" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "FREE TOOLS", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "nav-envin", + "type": "rectangle", + "x": 12, + "y": 388, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-envin-icon", + "type": "rectangle", + "x": 24, + "y": 398, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-envin-text", + "type": "text", + "x": 56, + "y": 398, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Envin", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-ideas", + "type": "rectangle", + "x": 12, + "y": 436, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-ideas-icon", + "type": "ellipse", + "x": 26, + "y": 446, + "width": 16, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-ideas-icon-filament", + "type": "line", + "x": 31, + "y": 462, + "width": 6, + "height": 4, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 3, + 4 + ], + [ + 6, + 0 + ] + ] + }, + { + "id": "nav-ideas-text", + "type": "text", + "x": 56, + "y": 446, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Ideas", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-extro", + "type": "rectangle", + "x": 12, + "y": 484, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-extro-icon", + "type": "rectangle", + "x": 24, + "y": 494, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-extro-text", + "type": "text", + "x": 56, + "y": 494, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Extro", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-emojai", + "type": "rectangle", + "x": 12, + "y": 532, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-emojai-icon", + "type": "ellipse", + "x": 24, + "y": 542, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-emojai-icon-smile", + "type": "line", + "x": 29, + "y": 554, + "width": 10, + "height": 4, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": { + "type": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 5, + 4 + ], + [ + 10, + 0 + ] + ] + }, + { + "id": "nav-emojai-text", + "type": "text", + "x": 56, + "y": 542, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Emojai", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-3", + "type": "line", + "x": 12, + "y": 588, + "width": 256, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 256, + 0 + ] + ] + }, + { + "id": "section-label-other-text", + "type": "text", + "x": 24, + "y": 600, + "width": 50, + "height": 14, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "section-other" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "OTHER", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "nav-home", + "type": "rectangle", + "x": 12, + "y": 620, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-other" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-home-icon-house", + "type": "line", + "x": 24, + "y": 630, + "width": 20, + "height": 10, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-other" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 10 + ], + [ + 10, + 0 + ], + [ + 20, + 10 + ] + ] + }, + { + "id": "nav-home-icon-body", + "type": "rectangle", + "x": 27, + "y": 638, + "width": 14, + "height": 12, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-other" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-home-text", + "type": "text", + "x": 56, + "y": 630, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-other" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Home", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-docs", + "type": "rectangle", + "x": 12, + "y": 668, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-other" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-docs-icon", + "type": "rectangle", + "x": 24, + "y": 678, + "width": 16, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-other" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-docs-icon-line1", + "type": "line", + "x": 27, + "y": 684, + "width": 10, + "height": 0, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-other" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 10, + 0 + ] + ] + }, + { + "id": "nav-docs-icon-line2", + "type": "line", + "x": 27, + "y": 689, + "width": 10, + "height": 0, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-other" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 10, + 0 + ] + ] + }, + { + "id": "nav-docs-icon-line3", + "type": "line", + "x": 27, + "y": 694, + "width": 6, + "height": 0, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-other" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 6, + 0 + ] + ] + }, + { + "id": "nav-docs-text", + "type": "text", + "x": 56, + "y": 678, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-other" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Docs", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-blog", + "type": "rectangle", + "x": 12, + "y": 716, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-other" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-blog-icon", + "type": "rectangle", + "x": 24, + "y": 726, + "width": 20, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-other" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-blog-icon-pen", + "type": "line", + "x": 36, + "y": 736, + "width": 10, + "height": 10, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-other" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 10 + ], + [ + 10, + 0 + ] + ] + }, + { + "id": "nav-blog-text", + "type": "text", + "x": 56, + "y": 726, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-other" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Blog", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-footer", + "type": "line", + "x": 12, + "y": 812, + "width": 256, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 256, + 0 + ] + ] + }, + { + "id": "user-footer", + "type": "rectangle", + "x": 12, + "y": 824, + "width": 256, + "height": 64, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-avatar", + "type": "ellipse", + "x": 24, + "y": 836, + "width": 40, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-avatar-initials", + "type": "text", + "x": 32, + "y": 848, + "width": 24, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "JD", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 10 + }, + { + "id": "user-status-dot", + "type": "ellipse", + "x": 54, + "y": 866, + "width": 12, + "height": 12, + "strokeColor": "#fafafa", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-name", + "type": "text", + "x": 76, + "y": 838, + "width": 120, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "John Doe", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 12 + }, + { + "id": "user-email", + "type": "text", + "x": 76, + "y": 858, + "width": 140, + "height": 14, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "john@example.com", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 10 + }, + { + "id": "dropdown-indicator", + "type": "line", + "x": 240, + "y": 850, + "width": 12, + "height": 8, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 6, + 8 + ], + [ + 12, + 0 + ] + ] + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/high/sidebar-dashboard.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/high/sidebar-dashboard.excalidraw new file mode 100644 index 0000000..8506fe2 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/high/sidebar-dashboard.excalidraw @@ -0,0 +1,1496 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "sidebar-container", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-area", + "type": "rectangle", + "x": 12, + "y": 16, + "width": 256, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-hexagon-outer", + "type": "diamond", + "x": 24, + "y": 22, + "width": 36, + "height": 36, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": { + "type": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-hexagon-inner", + "type": "diamond", + "x": 32, + "y": 30, + "width": 20, + "height": 20, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": { + "type": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 68, + "y": 30, + "width": 80, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "MCPGet", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 14 + }, + { + "id": "section-divider-1", + "type": "line", + "x": 12, + "y": 76, + "width": 256, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 256, + 0 + ] + ] + }, + { + "id": "section-label-platform-text", + "type": "text", + "x": 24, + "y": 88, + "width": 70, + "height": 14, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "section-platform" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "PLATFORM", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "nav-dashboard-active", + "type": "rectangle", + "x": 12, + "y": 108, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-platform" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-dashboard-icon-grid-tl", + "type": "rectangle", + "x": 24, + "y": 118, + "width": 8, + "height": 8, + "strokeColor": "#ffffff", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-platform" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-dashboard-icon-grid-tr", + "type": "rectangle", + "x": 34, + "y": 118, + "width": 8, + "height": 8, + "strokeColor": "#ffffff", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-platform" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-dashboard-icon-grid-bl", + "type": "rectangle", + "x": 24, + "y": 128, + "width": 8, + "height": 8, + "strokeColor": "#ffffff", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-platform" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-dashboard-icon-grid-br", + "type": "rectangle", + "x": 34, + "y": 128, + "width": 8, + "height": 8, + "strokeColor": "#ffffff", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-platform" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-dashboard-text", + "type": "text", + "x": 56, + "y": 118, + "width": 80, + "height": 20, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-platform" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Dashboard", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-ai-tools", + "type": "rectangle", + "x": 12, + "y": 156, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-platform" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-ai-tools-icon-sparkle-main", + "type": "diamond", + "x": 28, + "y": 170, + "width": 12, + "height": 12, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-platform" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-ai-tools-icon-sparkle-small", + "type": "diamond", + "x": 38, + "y": 166, + "width": 6, + "height": 6, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sidebar-group", + "nav-platform" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-ai-tools-text", + "type": "text", + "x": 56, + "y": 166, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-platform" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "AI Tools", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-2", + "type": "line", + "x": 12, + "y": 212, + "width": 256, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 256, + 0 + ] + ] + }, + { + "id": "section-label-manage-text", + "type": "text", + "x": 24, + "y": 224, + "width": 60, + "height": 14, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "section-manage" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "MANAGE", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "nav-settings-hover", + "type": "rectangle", + "x": 12, + "y": 244, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "sidebar-group", + "nav-manage" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-settings-icon-gear-outer", + "type": "ellipse", + "x": 24, + "y": 254, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-manage" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-settings-icon-gear-inner", + "type": "ellipse", + "x": 30, + "y": 260, + "width": 8, + "height": 8, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-manage" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-settings-icon-tooth-top", + "type": "rectangle", + "x": 32, + "y": 250, + "width": 4, + "height": 4, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-manage" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-settings-icon-tooth-bottom", + "type": "rectangle", + "x": 32, + "y": 274, + "width": 4, + "height": 4, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-manage" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-settings-icon-tooth-left", + "type": "rectangle", + "x": 20, + "y": 262, + "width": 4, + "height": 4, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-manage" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-settings-icon-tooth-right", + "type": "rectangle", + "x": 44, + "y": 262, + "width": 4, + "height": 4, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-manage" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-settings-text", + "type": "text", + "x": 56, + "y": 254, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-manage" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Settings", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-settings-badge-bg", + "type": "rectangle", + "x": 220, + "y": 256, + "width": 36, + "height": 18, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "sidebar-group", + "nav-manage" + ], + "roundness": { + "type": 3, + "value": 9 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-settings-badge-text", + "type": "text", + "x": 226, + "y": 258, + "width": 24, + "height": 14, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-manage" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "New", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "section-divider-3", + "type": "line", + "x": 12, + "y": 300, + "width": 256, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 256, + 0 + ] + ] + }, + { + "id": "section-label-dev-text", + "type": "text", + "x": 24, + "y": 312, + "width": 30, + "height": 14, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "section-dev" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "DEV", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "nav-demos", + "type": "rectangle", + "x": 12, + "y": 332, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-dev" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-demos-icon-layout-main", + "type": "rectangle", + "x": 24, + "y": 342, + "width": 10, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-dev" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-demos-icon-layout-top", + "type": "rectangle", + "x": 36, + "y": 342, + "width": 10, + "height": 8, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-dev" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-demos-icon-layout-bottom", + "type": "rectangle", + "x": 36, + "y": 354, + "width": 10, + "height": 8, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-dev" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-demos-text", + "type": "text", + "x": 56, + "y": 342, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-dev" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Demos", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-bottom", + "type": "line", + "x": 12, + "y": 700, + "width": 256, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 256, + 0 + ] + ] + }, + { + "id": "nav-support", + "type": "rectangle", + "x": 12, + "y": 712, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-bottom" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-support-icon-ring-outer", + "type": "ellipse", + "x": 24, + "y": 722, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-bottom" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-support-icon-ring-inner", + "type": "ellipse", + "x": 30, + "y": 728, + "width": 8, + "height": 8, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-bottom" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-support-icon-rope1", + "type": "line", + "x": 26, + "y": 724, + "width": 6, + "height": 6, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-bottom" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 6 + ], + [ + 6, + 0 + ] + ] + }, + { + "id": "nav-support-icon-rope2", + "type": "line", + "x": 36, + "y": 734, + "width": 6, + "height": 6, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-bottom" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 6 + ], + [ + 6, + 0 + ] + ] + }, + { + "id": "nav-support-text", + "type": "text", + "x": 56, + "y": 722, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-bottom" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Support", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-feedback", + "type": "rectangle", + "x": 12, + "y": 760, + "width": 256, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-bottom" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-feedback-icon-bubble", + "type": "rectangle", + "x": 24, + "y": 770, + "width": 20, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-bottom" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-feedback-icon-tail", + "type": "line", + "x": 24, + "y": 782, + "width": 6, + "height": 6, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-bottom" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 0, + 6 + ], + [ + 6, + 0 + ] + ] + }, + { + "id": "nav-feedback-icon-dots", + "type": "line", + "x": 28, + "y": 777, + "width": 12, + "height": 0, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 3, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-bottom" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 4, + 0 + ], + [ + 8, + 0 + ], + [ + 12, + 0 + ] + ] + }, + { + "id": "nav-feedback-text", + "type": "text", + "x": 56, + "y": 770, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-bottom" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Feedback", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-footer", + "type": "line", + "x": 12, + "y": 812, + "width": 256, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 256, + 0 + ] + ] + }, + { + "id": "user-footer", + "type": "rectangle", + "x": 12, + "y": 824, + "width": 256, + "height": 64, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-avatar", + "type": "ellipse", + "x": 24, + "y": 840, + "width": 40, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-avatar-initials", + "type": "text", + "x": 32, + "y": 852, + "width": 24, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "JD", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 10 + }, + { + "id": "user-name", + "type": "text", + "x": 76, + "y": 852, + "width": 120, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "John Doe", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "dropdown-indicator", + "type": "line", + "x": 240, + "y": 856, + "width": 12, + "height": 8, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 6, + 8 + ], + [ + 12, + 0 + ] + ] + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/low/auth-forgot-password.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/low/auth-forgot-password.excalidraw new file mode 100644 index 0000000..6db33d8 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/low/auth-forgot-password.excalidraw @@ -0,0 +1,284 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "left-column", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "left-column-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-column", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-column-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-placeholder", + "type": "rectangle", + "x": 300, + "y": 200, + "width": 120, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "left-column-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "forgot-password-title", + "type": "rectangle", + "x": 200, + "y": 300, + "width": 320, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "description-text", + "type": "rectangle", + "x": 200, + "y": 350, + "width": 320, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-input", + "type": "rectangle", + "x": 200, + "y": 420, + "width": 320, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "send-reset-button", + "type": "rectangle", + "x": 200, + "y": 500, + "width": 320, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "back-to-login-link", + "type": "rectangle", + "x": 280, + "y": 580, + "width": 160, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo", + "type": "rectangle", + "x": 1000, + "y": 400, + "width": 160, + "height": 60, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-column-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-tagline", + "type": "rectangle", + "x": 920, + "y": 480, + "width": 320, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-column-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/low/auth-join-org.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/low/auth-join-org.excalidraw new file mode 100644 index 0000000..c9a7d73 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/low/auth-join-org.excalidraw @@ -0,0 +1,384 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "left-column", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "left-column-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-column", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-column-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-placeholder", + "type": "rectangle", + "x": 300, + "y": 120, + "width": 120, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "left-column-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "join-org-title", + "type": "rectangle", + "x": 200, + "y": 200, + "width": 320, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "invitation-card", + "type": "rectangle", + "x": 180, + "y": 280, + "width": 360, + "height": 200, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "invitation-card-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "org-logo", + "type": "rectangle", + "x": 320, + "y": 300, + "width": 80, + "height": 80, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "invitation-card-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "org-name", + "type": "rectangle", + "x": 260, + "y": 400, + "width": 200, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "invitation-card-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "inviter-name", + "type": "rectangle", + "x": 280, + "y": 434, + "width": 160, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "invitation-card-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "role-badge", + "type": "rectangle", + "x": 320, + "y": 458, + "width": 80, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "invitation-card-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "accept-button", + "type": "rectangle", + "x": 200, + "y": 520, + "width": 320, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "decline-link", + "type": "rectangle", + "x": 320, + "y": 590, + "width": 80, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "wrong-account-link", + "type": "rectangle", + "x": 260, + "y": 640, + "width": 200, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo", + "type": "rectangle", + "x": 1000, + "y": 400, + "width": 160, + "height": 60, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-column-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-tagline", + "type": "rectangle", + "x": 920, + "y": 480, + "width": 320, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-column-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/low/auth-login.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/low/auth-login.excalidraw new file mode 100644 index 0000000..f6951b0 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/low/auth-login.excalidraw @@ -0,0 +1,434 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "left-column", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "left-column-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-column", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-column-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-placeholder", + "type": "rectangle", + "x": 300, + "y": 80, + "width": 120, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "left-column-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-title", + "type": "rectangle", + "x": 200, + "y": 180, + "width": 320, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-input", + "type": "rectangle", + "x": 200, + "y": 240, + "width": 320, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-input", + "type": "rectangle", + "x": 200, + "y": 300, + "width": 320, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "forgot-password-link", + "type": "rectangle", + "x": 400, + "y": 360, + "width": 120, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "login-button", + "type": "rectangle", + "x": 200, + "y": 400, + "width": 320, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-line", + "type": "rectangle", + "x": 200, + "y": 474, + "width": 320, + "height": 2, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-text", + "type": "rectangle", + "x": 300, + "y": 464, + "width": 120, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-google", + "type": "rectangle", + "x": 200, + "y": 520, + "width": 100, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-github", + "type": "rectangle", + "x": 310, + "y": 520, + "width": 100, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-apple", + "type": "rectangle", + "x": 420, + "y": 520, + "width": 100, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "signup-link", + "type": "rectangle", + "x": 240, + "y": 600, + "width": 240, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo", + "type": "rectangle", + "x": 1000, + "y": 400, + "width": 160, + "height": 60, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-column-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-tagline", + "type": "rectangle", + "x": 920, + "y": 480, + "width": 320, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-column-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/low/auth-register.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/low/auth-register.excalidraw new file mode 100644 index 0000000..d7f693b --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/low/auth-register.excalidraw @@ -0,0 +1,509 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "left-column", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "left-column-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-column", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-column-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-placeholder", + "type": "rectangle", + "x": 300, + "y": 60, + "width": 120, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "left-column-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "create-account-title", + "type": "rectangle", + "x": 200, + "y": 120, + "width": 320, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-input", + "type": "rectangle", + "x": 200, + "y": 180, + "width": 320, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-input", + "type": "rectangle", + "x": 200, + "y": 240, + "width": 320, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-input", + "type": "rectangle", + "x": 200, + "y": 300, + "width": 320, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "confirm-password-input", + "type": "rectangle", + "x": 200, + "y": 360, + "width": 320, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "terms-checkbox", + "type": "rectangle", + "x": 200, + "y": 420, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "terms-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "terms-text", + "type": "rectangle", + "x": 230, + "y": 420, + "width": 290, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "terms-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "create-account-button", + "type": "rectangle", + "x": 200, + "y": 460, + "width": 320, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-line", + "type": "rectangle", + "x": 200, + "y": 534, + "width": 320, + "height": 2, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-text", + "type": "rectangle", + "x": 300, + "y": 524, + "width": 120, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-google", + "type": "rectangle", + "x": 200, + "y": 580, + "width": 100, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-github", + "type": "rectangle", + "x": 310, + "y": 580, + "width": 100, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-apple", + "type": "rectangle", + "x": 420, + "y": 580, + "width": 100, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "login-link", + "type": "rectangle", + "x": 240, + "y": 660, + "width": 240, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo", + "type": "rectangle", + "x": 1000, + "y": 400, + "width": 160, + "height": 60, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-column-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-tagline", + "type": "rectangle", + "x": 920, + "y": 480, + "width": 320, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-column-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/low/dashboard-admin.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/low/dashboard-admin.excalidraw new file mode 100644 index 0000000..f7b2dc1 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/low/dashboard-admin.excalidraw @@ -0,0 +1,1809 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-home", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-home-icon", + "type": "rectangle", + "x": 32, + "y": 110, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-home-text", + "type": "rectangle", + "x": 64, + "y": 112, + "width": 50, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-icon", + "type": "rectangle", + "x": 32, + "y": 170, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-text", + "type": "rectangle", + "x": 64, + "y": 172, + "width": 50, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-orgs", + "type": "rectangle", + "x": 20, + "y": 220, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-orgs-icon", + "type": "rectangle", + "x": 32, + "y": 230, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-orgs-text", + "type": "rectangle", + "x": 64, + "y": 232, + "width": 100, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-customers", + "type": "rectangle", + "x": 20, + "y": 280, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-customers-icon", + "type": "rectangle", + "x": 32, + "y": 290, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-customers-text", + "type": "rectangle", + "x": 64, + "y": 292, + "width": 80, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "rectangle", + "x": 304, + "y": 20, + "width": 160, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-user-avatar", + "type": "rectangle", + "x": 1388, + "y": 16, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 16 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 264, + "height": 100, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-label", + "type": "rectangle", + "x": 324, + "y": 108, + "width": 80, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-value", + "type": "rectangle", + "x": 324, + "y": 136, + "width": 100, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2", + "type": "rectangle", + "x": 584, + "y": 88, + "width": 264, + "height": 100, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-label", + "type": "rectangle", + "x": 604, + "y": 108, + "width": 80, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-value", + "type": "rectangle", + "x": 604, + "y": 136, + "width": 60, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3", + "type": "rectangle", + "x": 864, + "y": 88, + "width": 264, + "height": 100, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-label", + "type": "rectangle", + "x": 884, + "y": 108, + "width": 60, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-value", + "type": "rectangle", + "x": 884, + "y": 136, + "width": 100, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4", + "type": "rectangle", + "x": 1144, + "y": 88, + "width": 264, + "height": 100, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-label", + "type": "rectangle", + "x": 1164, + "y": 108, + "width": 100, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-value", + "type": "rectangle", + "x": 1164, + "y": 136, + "width": 80, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-card", + "type": "rectangle", + "x": 304, + "y": 208, + "width": 720, + "height": 400, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-card-title", + "type": "rectangle", + "x": 324, + "y": 228, + "width": 120, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-1", + "type": "rectangle", + "x": 324, + "y": 268, + "width": 680, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-1-avatar", + "type": "rectangle", + "x": 340, + "y": 280, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-1-text", + "type": "rectangle", + "x": 376, + "y": 284, + "width": 300, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-1-time", + "type": "rectangle", + "x": 924, + "y": 284, + "width": 60, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-2", + "type": "rectangle", + "x": 324, + "y": 328, + "width": 680, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-2-avatar", + "type": "rectangle", + "x": 340, + "y": 340, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-2-text", + "type": "rectangle", + "x": 376, + "y": 344, + "width": 280, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-2-time", + "type": "rectangle", + "x": 924, + "y": 344, + "width": 60, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-3", + "type": "rectangle", + "x": 324, + "y": 388, + "width": 680, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-3-avatar", + "type": "rectangle", + "x": 340, + "y": 400, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-3-text", + "type": "rectangle", + "x": 376, + "y": 404, + "width": 320, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-3-time", + "type": "rectangle", + "x": 924, + "y": 404, + "width": 60, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-4", + "type": "rectangle", + "x": 324, + "y": 448, + "width": 680, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-4-avatar", + "type": "rectangle", + "x": 340, + "y": 460, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-4-text", + "type": "rectangle", + "x": 376, + "y": 464, + "width": 260, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-4-time", + "type": "rectangle", + "x": 924, + "y": 464, + "width": 60, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-5", + "type": "rectangle", + "x": 324, + "y": 508, + "width": 680, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-5-avatar", + "type": "rectangle", + "x": 340, + "y": 520, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-5-text", + "type": "rectangle", + "x": 376, + "y": 524, + "width": 340, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-5-time", + "type": "rectangle", + "x": 924, + "y": 524, + "width": 60, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-6", + "type": "rectangle", + "x": 324, + "y": 568, + "width": 680, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-6-avatar", + "type": "rectangle", + "x": 340, + "y": 580, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-6-text", + "type": "rectangle", + "x": 376, + "y": 584, + "width": 290, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-6-time", + "type": "rectangle", + "x": 924, + "y": 584, + "width": 60, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-actions-card", + "type": "rectangle", + "x": 1044, + "y": 208, + "width": 364, + "height": 400, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-actions-title", + "type": "rectangle", + "x": 1064, + "y": 228, + "width": 120, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-1", + "type": "rectangle", + "x": 1064, + "y": 268, + "width": 324, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-1-text", + "type": "rectangle", + "x": 1144, + "y": 284, + "width": 160, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-2", + "type": "rectangle", + "x": 1064, + "y": 332, + "width": 324, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-2-text", + "type": "rectangle", + "x": 1144, + "y": 348, + "width": 140, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-3", + "type": "rectangle", + "x": 1064, + "y": 396, + "width": 324, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-3-text", + "type": "rectangle", + "x": 1144, + "y": 412, + "width": 120, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-4", + "type": "rectangle", + "x": 1064, + "y": 460, + "width": 324, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-4-text", + "type": "rectangle", + "x": 1144, + "y": 476, + "width": 100, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-5", + "type": "rectangle", + "x": 1064, + "y": 524, + "width": 324, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-5-text", + "type": "rectangle", + "x": 1144, + "y": 540, + "width": 140, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/low/dashboard-org.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/low/dashboard-org.excalidraw new file mode 100644 index 0000000..bef3132 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/low/dashboard-org.excalidraw @@ -0,0 +1,1384 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-overview", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-overview-icon", + "type": "rectangle", + "x": 32, + "y": 110, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-overview-text", + "type": "rectangle", + "x": 64, + "y": 112, + "width": 80, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-icon", + "type": "rectangle", + "x": 32, + "y": 170, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-text", + "type": "rectangle", + "x": 64, + "y": 172, + "width": 70, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings", + "type": "rectangle", + "x": 20, + "y": 220, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-icon", + "type": "rectangle", + "x": 32, + "y": 230, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-text", + "type": "rectangle", + "x": 64, + "y": 232, + "width": 60, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing", + "type": "rectangle", + "x": 20, + "y": 280, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing-icon", + "type": "rectangle", + "x": 32, + "y": 290, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing-text", + "type": "rectangle", + "x": 64, + "y": 292, + "width": 50, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "rectangle", + "x": 304, + "y": 20, + "width": 200, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-user-avatar", + "type": "rectangle", + "x": 1388, + "y": 16, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 16 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 264, + "height": 100, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-label", + "type": "rectangle", + "x": 324, + "y": 108, + "width": 100, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-value", + "type": "rectangle", + "x": 324, + "y": 136, + "width": 80, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2", + "type": "rectangle", + "x": 584, + "y": 88, + "width": 264, + "height": 100, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-label", + "type": "rectangle", + "x": 604, + "y": 108, + "width": 100, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-value", + "type": "rectangle", + "x": 604, + "y": 136, + "width": 60, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3", + "type": "rectangle", + "x": 864, + "y": 88, + "width": 264, + "height": 100, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-label", + "type": "rectangle", + "x": 884, + "y": 108, + "width": 80, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-value", + "type": "rectangle", + "x": 884, + "y": 136, + "width": 100, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4", + "type": "rectangle", + "x": 1144, + "y": 88, + "width": 264, + "height": 100, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-label", + "type": "rectangle", + "x": 1164, + "y": 108, + "width": 100, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-value", + "type": "rectangle", + "x": 1164, + "y": 136, + "width": 80, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-card", + "type": "rectangle", + "x": 304, + "y": 208, + "width": 1104, + "height": 320, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-title", + "type": "rectangle", + "x": 324, + "y": 228, + "width": 120, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-area", + "type": "rectangle", + "x": 324, + "y": 268, + "width": 1064, + "height": 240, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-line-1", + "type": "rectangle", + "x": 324, + "y": 380, + "width": 1064, + "height": 4, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-line-2", + "type": "rectangle", + "x": 324, + "y": 340, + "width": 1064, + "height": 4, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-line-3", + "type": "rectangle", + "x": 324, + "y": 420, + "width": 1064, + "height": 4, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-line-4", + "type": "rectangle", + "x": 324, + "y": 460, + "width": 1064, + "height": 4, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1", + "type": "rectangle", + "x": 304, + "y": 548, + "width": 540, + "height": 240, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-title", + "type": "rectangle", + "x": 324, + "y": 568, + "width": 100, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-area", + "type": "rectangle", + "x": 324, + "y": 608, + "width": 500, + "height": 160, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-1", + "type": "rectangle", + "x": 364, + "y": 668, + "width": 40, + "height": 80, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-2", + "type": "rectangle", + "x": 444, + "y": 648, + "width": 40, + "height": 100, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-3", + "type": "rectangle", + "x": 524, + "y": 688, + "width": 40, + "height": 60, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-4", + "type": "rectangle", + "x": 604, + "y": 628, + "width": 40, + "height": 120, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-5", + "type": "rectangle", + "x": 684, + "y": 658, + "width": 40, + "height": 90, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-6", + "type": "rectangle", + "x": 764, + "y": 678, + "width": 40, + "height": 70, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2", + "type": "rectangle", + "x": 864, + "y": 548, + "width": 544, + "height": 240, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-title", + "type": "rectangle", + "x": 884, + "y": 568, + "width": 120, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-area", + "type": "rectangle", + "x": 884, + "y": 608, + "width": 504, + "height": 160, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-pie", + "type": "rectangle", + "x": 1036, + "y": 628, + "width": 120, + "height": 120, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 60 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-pie-inner", + "type": "rectangle", + "x": 1066, + "y": 658, + "width": 60, + "height": 60, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 30 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/low/dashboard-user.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/low/dashboard-user.excalidraw new file mode 100644 index 0000000..ca5e8ee --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/low/dashboard-user.excalidraw @@ -0,0 +1,1109 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-icon", + "type": "rectangle", + "x": 32, + "y": 110, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-text", + "type": "rectangle", + "x": 64, + "y": 112, + "width": 80, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-aitools", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-aitools-icon", + "type": "rectangle", + "x": 32, + "y": 170, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-aitools-text", + "type": "rectangle", + "x": 64, + "y": 172, + "width": 60, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings", + "type": "rectangle", + "x": 20, + "y": 220, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-icon", + "type": "rectangle", + "x": 32, + "y": 230, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-text", + "type": "rectangle", + "x": 64, + "y": 232, + "width": 60, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-support-item", + "type": "rectangle", + "x": 20, + "y": 780, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-support-icon", + "type": "rectangle", + "x": 32, + "y": 790, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-support-text", + "type": "rectangle", + "x": 64, + "y": 792, + "width": 100, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "rectangle", + "x": 304, + "y": 20, + "width": 120, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-user-avatar", + "type": "rectangle", + "x": 1388, + "y": 16, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 16 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-card", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 1112, + "height": 120, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-title", + "type": "rectangle", + "x": 324, + "y": 108, + "width": 200, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-subtitle", + "type": "rectangle", + "x": 324, + "y": 144, + "width": 400, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-subtitle-2", + "type": "rectangle", + "x": 324, + "y": 168, + "width": 300, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1", + "type": "rectangle", + "x": 304, + "y": 228, + "width": 360, + "height": 200, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-icon", + "type": "rectangle", + "x": 324, + "y": 248, + "width": 48, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-title", + "type": "rectangle", + "x": 324, + "y": 312, + "width": 100, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-desc", + "type": "rectangle", + "x": 324, + "y": 344, + "width": 320, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-desc-2", + "type": "rectangle", + "x": 324, + "y": 368, + "width": 280, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-button", + "type": "rectangle", + "x": 324, + "y": 396, + "width": 100, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2", + "type": "rectangle", + "x": 680, + "y": 228, + "width": 360, + "height": 200, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-icon", + "type": "rectangle", + "x": 700, + "y": 248, + "width": 48, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-title", + "type": "rectangle", + "x": 700, + "y": 312, + "width": 140, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-desc", + "type": "rectangle", + "x": 700, + "y": 344, + "width": 320, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-desc-2", + "type": "rectangle", + "x": 700, + "y": 368, + "width": 260, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-button", + "type": "rectangle", + "x": 700, + "y": 396, + "width": 100, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3", + "type": "rectangle", + "x": 1056, + "y": 228, + "width": 360, + "height": 200, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-icon", + "type": "rectangle", + "x": 1076, + "y": 248, + "width": 48, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-title", + "type": "rectangle", + "x": 1076, + "y": 312, + "width": 100, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-desc", + "type": "rectangle", + "x": 1076, + "y": 344, + "width": 320, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-desc-2", + "type": "rectangle", + "x": 1076, + "y": 368, + "width": 280, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-button", + "type": "rectangle", + "x": 1076, + "y": 396, + "width": 100, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/low/data-table-invitations.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/low/data-table-invitations.excalidraw new file mode 100644 index 0000000..c68dae4 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/low/data-table-invitations.excalidraw @@ -0,0 +1,2300 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-switcher", + "type": "rectangle", + "x": 20, + "y": 80, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-avatar", + "type": "rectangle", + "x": 32, + "y": 88, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-name", + "type": "rectangle", + "x": 64, + "y": 92, + "width": 100, + "height": 16, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-section-label", + "type": "rectangle", + "x": 20, + "y": 140, + "width": 60, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-active", + "type": "rectangle", + "x": 20, + "y": 208, + "width": 240, + "height": 40, + "strokeColor": "#f97316", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-icon", + "type": "rectangle", + "x": 32, + "y": 218, + "width": 20, + "height": 20, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-label", + "type": "rectangle", + "x": 60, + "y": 222, + "width": 60, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings", + "type": "rectangle", + "x": 20, + "y": 256, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing", + "type": "rectangle", + "x": 20, + "y": 304, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "rectangle", + "x": 304, + "y": 20, + "width": 100, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invite-button", + "type": "rectangle", + "x": 1296, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tabs-container", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 1112, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-members", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 120, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-members-label", + "type": "rectangle", + "x": 324, + "y": 104, + "width": 80, + "height": 16, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-invitations-active", + "type": "rectangle", + "x": 424, + "y": 88, + "width": 160, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-invitations-label", + "type": "rectangle", + "x": 444, + "y": 104, + "width": 120, + "height": 16, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-invitations-underline", + "type": "rectangle", + "x": 424, + "y": 132, + "width": 160, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-container", + "type": "rectangle", + "x": 304, + "y": 152, + "width": 1112, + "height": 716, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "toolbar", + "type": "rectangle", + "x": 304, + "y": 152, + "width": 1112, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-input", + "type": "rectangle", + "x": 316, + "y": 162, + "width": 240, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-status", + "type": "rectangle", + "x": 568, + "y": 162, + "width": 120, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-status-arrow", + "type": "rectangle", + "x": 668, + "y": 176, + "width": 8, + "height": 8, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-header-row", + "type": "rectangle", + "x": 304, + "y": 208, + "width": 1112, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-email-col", + "type": "rectangle", + "x": 320, + "y": 226, + "width": 50, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-role-col", + "type": "rectangle", + "x": 480, + "y": 226, + "width": 40, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invitedby-col", + "type": "rectangle", + "x": 620, + "y": 226, + "width": 70, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-sent-col", + "type": "rectangle", + "x": 780, + "y": 226, + "width": 40, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-expires-col", + "type": "rectangle", + "x": 900, + "y": 226, + "width": 50, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-status-col", + "type": "rectangle", + "x": 1040, + "y": 226, + "width": 50, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-actions-col", + "type": "rectangle", + "x": 1340, + "y": 226, + "width": 50, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-1", + "type": "rectangle", + "x": 304, + "y": 256, + "width": 1112, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-email", + "type": "rectangle", + "x": 320, + "y": 278, + "width": 140, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-badge", + "type": "rectangle", + "x": 480, + "y": 274, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-invitedby", + "type": "rectangle", + "x": 620, + "y": 278, + "width": 100, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-sent", + "type": "rectangle", + "x": 780, + "y": 278, + "width": 70, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-expires", + "type": "rectangle", + "x": 900, + "y": 278, + "width": 70, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status-badge", + "type": "rectangle", + "x": 1040, + "y": 274, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-resend", + "type": "rectangle", + "x": 1320, + "y": 274, + "width": 28, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-cancel", + "type": "rectangle", + "x": 1356, + "y": 274, + "width": 28, + "height": 20, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-2", + "type": "rectangle", + "x": 304, + "y": 312, + "width": 1112, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-email", + "type": "rectangle", + "x": 320, + "y": 334, + "width": 120, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-badge", + "type": "rectangle", + "x": 480, + "y": 330, + "width": 50, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-invitedby", + "type": "rectangle", + "x": 620, + "y": 334, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-sent", + "type": "rectangle", + "x": 780, + "y": 334, + "width": 70, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-expires", + "type": "rectangle", + "x": 900, + "y": 334, + "width": 70, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status-badge", + "type": "rectangle", + "x": 1040, + "y": 330, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-resend", + "type": "rectangle", + "x": 1320, + "y": 330, + "width": 28, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-cancel", + "type": "rectangle", + "x": 1356, + "y": 330, + "width": 28, + "height": 20, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-3", + "type": "rectangle", + "x": 304, + "y": 368, + "width": 1112, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-email", + "type": "rectangle", + "x": 320, + "y": 390, + "width": 150, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-badge", + "type": "rectangle", + "x": 480, + "y": 386, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-invitedby", + "type": "rectangle", + "x": 620, + "y": 390, + "width": 90, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-sent", + "type": "rectangle", + "x": 780, + "y": 390, + "width": 70, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-expires", + "type": "rectangle", + "x": 900, + "y": 390, + "width": 70, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-status-badge", + "type": "rectangle", + "x": 1040, + "y": 386, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-resend", + "type": "rectangle", + "x": 1320, + "y": 386, + "width": 28, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-action-cancel", + "type": "rectangle", + "x": 1356, + "y": 386, + "width": 28, + "height": 20, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-4", + "type": "rectangle", + "x": 304, + "y": 424, + "width": 1112, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-email", + "type": "rectangle", + "x": 320, + "y": 446, + "width": 130, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-role-badge", + "type": "rectangle", + "x": 480, + "y": 442, + "width": 50, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-invitedby", + "type": "rectangle", + "x": 620, + "y": 446, + "width": 110, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-sent", + "type": "rectangle", + "x": 780, + "y": 446, + "width": 70, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-expires", + "type": "rectangle", + "x": 900, + "y": 446, + "width": 70, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-status-badge", + "type": "rectangle", + "x": 1040, + "y": 442, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-action-resend", + "type": "rectangle", + "x": 1320, + "y": 442, + "width": 28, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-action-cancel", + "type": "rectangle", + "x": 1356, + "y": 442, + "width": 28, + "height": 20, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-5", + "type": "rectangle", + "x": 304, + "y": 480, + "width": 1112, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-email", + "type": "rectangle", + "x": 320, + "y": 502, + "width": 145, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-role-badge", + "type": "rectangle", + "x": 480, + "y": 498, + "width": 50, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-invitedby", + "type": "rectangle", + "x": 620, + "y": 502, + "width": 95, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-sent", + "type": "rectangle", + "x": 780, + "y": 502, + "width": 70, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-expires", + "type": "rectangle", + "x": 900, + "y": 502, + "width": 70, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-status-badge", + "type": "rectangle", + "x": 1040, + "y": 498, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-action-resend", + "type": "rectangle", + "x": 1320, + "y": 498, + "width": 28, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-action-cancel", + "type": "rectangle", + "x": 1356, + "y": 498, + "width": 28, + "height": 20, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "empty-rows-area", + "type": "rectangle", + "x": 304, + "y": 536, + "width": 1112, + "height": 284, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-bar", + "type": "rectangle", + "x": 304, + "y": 820, + "width": 1112, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-info", + "type": "rectangle", + "x": 328, + "y": 836, + "width": 80, + "height": 16, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-prev", + "type": "rectangle", + "x": 1284, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-1", + "type": "rectangle", + "x": 1324, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-next", + "type": "rectangle", + "x": 1364, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/low/data-table-members.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/low/data-table-members.excalidraw new file mode 100644 index 0000000..7dfc5ad --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/low/data-table-members.excalidraw @@ -0,0 +1,2323 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-switcher", + "type": "rectangle", + "x": 20, + "y": 80, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-avatar", + "type": "rectangle", + "x": 32, + "y": 88, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-name", + "type": "rectangle", + "x": 64, + "y": 92, + "width": 100, + "height": 16, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-section-label", + "type": "rectangle", + "x": 20, + "y": 140, + "width": 60, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-active", + "type": "rectangle", + "x": 20, + "y": 208, + "width": 240, + "height": 40, + "strokeColor": "#f97316", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-icon", + "type": "rectangle", + "x": 32, + "y": 218, + "width": 20, + "height": 20, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-label", + "type": "rectangle", + "x": 60, + "y": 222, + "width": 60, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings", + "type": "rectangle", + "x": 20, + "y": 256, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing", + "type": "rectangle", + "x": 20, + "y": 304, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "rectangle", + "x": 304, + "y": 20, + "width": 100, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invite-button", + "type": "rectangle", + "x": 1296, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tabs-container", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 1112, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-members-active", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 120, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-members-label", + "type": "rectangle", + "x": 324, + "y": 104, + "width": 80, + "height": 16, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-members-underline", + "type": "rectangle", + "x": 304, + "y": 132, + "width": 120, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-invitations", + "type": "rectangle", + "x": 424, + "y": 88, + "width": 160, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-invitations-label", + "type": "rectangle", + "x": 444, + "y": 104, + "width": 120, + "height": 16, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-container", + "type": "rectangle", + "x": 304, + "y": 152, + "width": 1112, + "height": 716, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "toolbar", + "type": "rectangle", + "x": 304, + "y": 152, + "width": 1112, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-input", + "type": "rectangle", + "x": 316, + "y": 162, + "width": 240, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role", + "type": "rectangle", + "x": 568, + "y": 162, + "width": 120, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role-arrow", + "type": "rectangle", + "x": 668, + "y": 176, + "width": 8, + "height": 8, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-header-row", + "type": "rectangle", + "x": 304, + "y": 208, + "width": 1112, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-member-col", + "type": "rectangle", + "x": 320, + "y": 226, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-role-col", + "type": "rectangle", + "x": 600, + "y": 226, + "width": 40, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-joined-col", + "type": "rectangle", + "x": 880, + "y": 226, + "width": 50, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-actions-col", + "type": "rectangle", + "x": 1340, + "y": 226, + "width": 50, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-1", + "type": "rectangle", + "x": 304, + "y": 256, + "width": 1112, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-avatar", + "type": "rectangle", + "x": 320, + "y": 274, + "width": 28, + "height": 28, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 14 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-name", + "type": "rectangle", + "x": 360, + "y": 270, + "width": 100, + "height": 14, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-email", + "type": "rectangle", + "x": 360, + "y": 288, + "width": 140, + "height": 10, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-dropdown", + "type": "rectangle", + "x": 600, + "y": 276, + "width": 100, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-arrow", + "type": "rectangle", + "x": 680, + "y": 284, + "width": 8, + "height": 8, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-joined", + "type": "rectangle", + "x": 880, + "y": 282, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-remove", + "type": "rectangle", + "x": 1356, + "y": 278, + "width": 24, + "height": 20, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-2", + "type": "rectangle", + "x": 304, + "y": 320, + "width": 1112, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-avatar", + "type": "rectangle", + "x": 320, + "y": 338, + "width": 28, + "height": 28, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 14 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-name", + "type": "rectangle", + "x": 360, + "y": 334, + "width": 80, + "height": 14, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-email", + "type": "rectangle", + "x": 360, + "y": 352, + "width": 120, + "height": 10, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-dropdown", + "type": "rectangle", + "x": 600, + "y": 340, + "width": 100, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-arrow", + "type": "rectangle", + "x": 680, + "y": 348, + "width": 8, + "height": 8, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-joined", + "type": "rectangle", + "x": 880, + "y": 346, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-remove", + "type": "rectangle", + "x": 1356, + "y": 342, + "width": 24, + "height": 20, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-3", + "type": "rectangle", + "x": 304, + "y": 384, + "width": 1112, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-avatar", + "type": "rectangle", + "x": 320, + "y": 402, + "width": 28, + "height": 28, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 14 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-name", + "type": "rectangle", + "x": 360, + "y": 398, + "width": 110, + "height": 14, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-email", + "type": "rectangle", + "x": 360, + "y": 416, + "width": 150, + "height": 10, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-dropdown", + "type": "rectangle", + "x": 600, + "y": 404, + "width": 100, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-arrow", + "type": "rectangle", + "x": 680, + "y": 412, + "width": 8, + "height": 8, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-joined", + "type": "rectangle", + "x": 880, + "y": 410, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-remove", + "type": "rectangle", + "x": 1356, + "y": 406, + "width": 24, + "height": 20, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-4", + "type": "rectangle", + "x": 304, + "y": 448, + "width": 1112, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-avatar", + "type": "rectangle", + "x": 320, + "y": 466, + "width": 28, + "height": 28, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": { + "type": 3, + "value": 14 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-name", + "type": "rectangle", + "x": 360, + "y": 462, + "width": 90, + "height": 14, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-email", + "type": "rectangle", + "x": 360, + "y": 480, + "width": 130, + "height": 10, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-role-dropdown", + "type": "rectangle", + "x": 600, + "y": 468, + "width": 100, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-role-arrow", + "type": "rectangle", + "x": 680, + "y": 476, + "width": 8, + "height": 8, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-joined", + "type": "rectangle", + "x": 880, + "y": 474, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-remove", + "type": "rectangle", + "x": 1356, + "y": 470, + "width": 24, + "height": 20, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-5", + "type": "rectangle", + "x": 304, + "y": 512, + "width": 1112, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-avatar", + "type": "rectangle", + "x": 320, + "y": 530, + "width": 28, + "height": 28, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": { + "type": 3, + "value": 14 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-name", + "type": "rectangle", + "x": 360, + "y": 526, + "width": 95, + "height": 14, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-email", + "type": "rectangle", + "x": 360, + "y": 544, + "width": 135, + "height": 10, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-role-dropdown", + "type": "rectangle", + "x": 600, + "y": 532, + "width": 100, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-role-arrow", + "type": "rectangle", + "x": 680, + "y": 540, + "width": 8, + "height": 8, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-joined", + "type": "rectangle", + "x": 880, + "y": 538, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-remove", + "type": "rectangle", + "x": 1356, + "y": 534, + "width": 24, + "height": 20, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-6", + "type": "rectangle", + "x": 304, + "y": 576, + "width": 1112, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-6-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-6-avatar", + "type": "rectangle", + "x": 320, + "y": 594, + "width": 28, + "height": 28, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-6-group" + ], + "roundness": { + "type": 3, + "value": 14 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-6-name", + "type": "rectangle", + "x": 360, + "y": 590, + "width": 85, + "height": 14, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-6-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-6-email", + "type": "rectangle", + "x": 360, + "y": 608, + "width": 125, + "height": 10, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-6-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-6-role-dropdown", + "type": "rectangle", + "x": 600, + "y": 596, + "width": 100, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-6-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-6-role-arrow", + "type": "rectangle", + "x": 680, + "y": 604, + "width": 8, + "height": 8, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-6-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-6-joined", + "type": "rectangle", + "x": 880, + "y": 602, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-6-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-6-remove", + "type": "rectangle", + "x": 1356, + "y": 598, + "width": 24, + "height": 20, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-6-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "empty-rows-area", + "type": "rectangle", + "x": 304, + "y": 640, + "width": 1112, + "height": 180, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-bar", + "type": "rectangle", + "x": 304, + "y": 820, + "width": 1112, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-info", + "type": "rectangle", + "x": 328, + "y": 836, + "width": 100, + "height": 16, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-prev", + "type": "rectangle", + "x": 1244, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-1", + "type": "rectangle", + "x": 1284, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-2", + "type": "rectangle", + "x": 1324, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-next", + "type": "rectangle", + "x": 1364, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/low/data-table-users.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/low/data-table-users.excalidraw new file mode 100644 index 0000000..a6dc7b6 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/low/data-table-users.excalidraw @@ -0,0 +1,3005 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-section-label", + "type": "rectangle", + "x": 20, + "y": 80, + "width": 60, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-active", + "type": "rectangle", + "x": 20, + "y": 148, + "width": 240, + "height": 40, + "strokeColor": "#f97316", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-icon", + "type": "rectangle", + "x": 32, + "y": 158, + "width": 20, + "height": 20, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-label", + "type": "rectangle", + "x": 60, + "y": 162, + "width": 50, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings", + "type": "rectangle", + "x": 20, + "y": 196, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-analytics", + "type": "rectangle", + "x": 20, + "y": 244, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "rectangle", + "x": 304, + "y": 20, + "width": 80, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-count-badge", + "type": "rectangle", + "x": 392, + "y": 20, + "width": 48, + "height": 24, + "strokeColor": "transparent", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-container", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 1112, + "height": 780, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "toolbar", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 1112, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-input", + "type": "rectangle", + "x": 316, + "y": 98, + "width": 200, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role", + "type": "rectangle", + "x": 528, + "y": 98, + "width": 100, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role-arrow", + "type": "rectangle", + "x": 608, + "y": 112, + "width": 8, + "height": 8, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-2fa", + "type": "rectangle", + "x": 640, + "y": 98, + "width": 100, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-2fa-arrow", + "type": "rectangle", + "x": 720, + "y": 112, + "width": 8, + "height": 8, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-status", + "type": "rectangle", + "x": 752, + "y": 98, + "width": 100, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-status-arrow", + "type": "rectangle", + "x": 832, + "y": 112, + "width": 8, + "height": 8, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-date", + "type": "rectangle", + "x": 864, + "y": 98, + "width": 120, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-date-arrow", + "type": "rectangle", + "x": 964, + "y": 112, + "width": 8, + "height": 8, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "view-options", + "type": "rectangle", + "x": 1316, + "y": 98, + "width": 88, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-header-row", + "type": "rectangle", + "x": 304, + "y": 144, + "width": 1112, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-checkbox", + "type": "rectangle", + "x": 320, + "y": 158, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-user-col", + "type": "rectangle", + "x": 360, + "y": 162, + "width": 100, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-role-col", + "type": "rectangle", + "x": 580, + "y": 162, + "width": 40, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-2fa-col", + "type": "rectangle", + "x": 700, + "y": 162, + "width": 30, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-status-col", + "type": "rectangle", + "x": 820, + "y": 162, + "width": 50, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-created-col", + "type": "rectangle", + "x": 980, + "y": 162, + "width": 60, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-actions-col", + "type": "rectangle", + "x": 1340, + "y": 162, + "width": 50, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-1", + "type": "rectangle", + "x": 304, + "y": 192, + "width": 1112, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-checkbox", + "type": "rectangle", + "x": 320, + "y": 210, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-avatar", + "type": "rectangle", + "x": 360, + "y": 206, + "width": 28, + "height": 28, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 14 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-name", + "type": "rectangle", + "x": 396, + "y": 206, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-email", + "type": "rectangle", + "x": 396, + "y": 222, + "width": 120, + "height": 10, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-badge", + "type": "rectangle", + "x": 580, + "y": 210, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-2fa-icon", + "type": "rectangle", + "x": 700, + "y": 210, + "width": 20, + "height": 20, + "strokeColor": "#22c55e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status-badge", + "type": "rectangle", + "x": 820, + "y": 210, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-created", + "type": "rectangle", + "x": 980, + "y": 214, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-actions", + "type": "rectangle", + "x": 1356, + "y": 210, + "width": 24, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-2", + "type": "rectangle", + "x": 304, + "y": 248, + "width": 1112, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-checkbox", + "type": "rectangle", + "x": 320, + "y": 266, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-avatar", + "type": "rectangle", + "x": 360, + "y": 262, + "width": 28, + "height": 28, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 14 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-name", + "type": "rectangle", + "x": 396, + "y": 262, + "width": 100, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-email", + "type": "rectangle", + "x": 396, + "y": 278, + "width": 140, + "height": 10, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-badge", + "type": "rectangle", + "x": 580, + "y": 266, + "width": 50, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-2fa-icon", + "type": "rectangle", + "x": 700, + "y": 266, + "width": 20, + "height": 20, + "strokeColor": "#22c55e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status-badge", + "type": "rectangle", + "x": 820, + "y": 266, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-created", + "type": "rectangle", + "x": 980, + "y": 270, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-actions", + "type": "rectangle", + "x": 1356, + "y": 266, + "width": 24, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-3", + "type": "rectangle", + "x": 304, + "y": 304, + "width": 1112, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-checkbox", + "type": "rectangle", + "x": 320, + "y": 322, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-avatar", + "type": "rectangle", + "x": 360, + "y": 318, + "width": 28, + "height": 28, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 14 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-name", + "type": "rectangle", + "x": 396, + "y": 318, + "width": 90, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-email", + "type": "rectangle", + "x": 396, + "y": 334, + "width": 110, + "height": 10, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-badge", + "type": "rectangle", + "x": 580, + "y": 322, + "width": 50, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-2fa-icon", + "type": "rectangle", + "x": 700, + "y": 322, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-status-badge", + "type": "rectangle", + "x": 820, + "y": 322, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-created", + "type": "rectangle", + "x": 980, + "y": 326, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-actions", + "type": "rectangle", + "x": 1356, + "y": 322, + "width": 24, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-4", + "type": "rectangle", + "x": 304, + "y": 360, + "width": 1112, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-checkbox", + "type": "rectangle", + "x": 320, + "y": 378, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-avatar", + "type": "rectangle", + "x": 360, + "y": 374, + "width": 28, + "height": 28, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": { + "type": 3, + "value": 14 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-name", + "type": "rectangle", + "x": 396, + "y": 374, + "width": 70, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-email", + "type": "rectangle", + "x": 396, + "y": 390, + "width": 130, + "height": 10, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-role-badge", + "type": "rectangle", + "x": 580, + "y": 378, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-2fa-icon", + "type": "rectangle", + "x": 700, + "y": 378, + "width": 20, + "height": 20, + "strokeColor": "#22c55e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-status-badge", + "type": "rectangle", + "x": 820, + "y": 378, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-created", + "type": "rectangle", + "x": 980, + "y": 382, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-actions", + "type": "rectangle", + "x": 1356, + "y": 378, + "width": 24, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-5", + "type": "rectangle", + "x": 304, + "y": 416, + "width": 1112, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-checkbox", + "type": "rectangle", + "x": 320, + "y": 434, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-avatar", + "type": "rectangle", + "x": 360, + "y": 430, + "width": 28, + "height": 28, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": { + "type": 3, + "value": 14 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-name", + "type": "rectangle", + "x": 396, + "y": 430, + "width": 85, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-email", + "type": "rectangle", + "x": 396, + "y": 446, + "width": 125, + "height": 10, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-role-badge", + "type": "rectangle", + "x": 580, + "y": 434, + "width": 50, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-2fa-icon", + "type": "rectangle", + "x": 700, + "y": 434, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-status-badge", + "type": "rectangle", + "x": 820, + "y": 434, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-created", + "type": "rectangle", + "x": 980, + "y": 438, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-actions", + "type": "rectangle", + "x": 1356, + "y": 434, + "width": 24, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-6", + "type": "rectangle", + "x": 304, + "y": 472, + "width": 1112, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-6-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-6-checkbox", + "type": "rectangle", + "x": 320, + "y": 490, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-6-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-6-avatar", + "type": "rectangle", + "x": 360, + "y": 486, + "width": 28, + "height": 28, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-6-group" + ], + "roundness": { + "type": 3, + "value": 14 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-6-name", + "type": "rectangle", + "x": 396, + "y": 486, + "width": 95, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-6-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-6-email", + "type": "rectangle", + "x": 396, + "y": 502, + "width": 135, + "height": 10, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-6-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-6-role-badge", + "type": "rectangle", + "x": 580, + "y": 490, + "width": 50, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-6-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-6-2fa-icon", + "type": "rectangle", + "x": 700, + "y": 490, + "width": 20, + "height": 20, + "strokeColor": "#22c55e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-6-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-6-status-badge", + "type": "rectangle", + "x": 820, + "y": 490, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-6-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-6-created", + "type": "rectangle", + "x": 980, + "y": 494, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-6-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-6-actions", + "type": "rectangle", + "x": 1356, + "y": 490, + "width": 24, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-6-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-7", + "type": "rectangle", + "x": 304, + "y": 528, + "width": 1112, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-7-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-7-checkbox", + "type": "rectangle", + "x": 320, + "y": 546, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-7-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-7-avatar", + "type": "rectangle", + "x": 360, + "y": 542, + "width": 28, + "height": 28, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-7-group" + ], + "roundness": { + "type": 3, + "value": 14 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-7-name", + "type": "rectangle", + "x": 396, + "y": 542, + "width": 75, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-7-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-7-email", + "type": "rectangle", + "x": 396, + "y": 558, + "width": 115, + "height": 10, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-7-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-7-role-badge", + "type": "rectangle", + "x": 580, + "y": 546, + "width": 50, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-7-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-7-2fa-icon", + "type": "rectangle", + "x": 700, + "y": 546, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-7-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-7-status-badge", + "type": "rectangle", + "x": 820, + "y": 546, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-7-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-7-created", + "type": "rectangle", + "x": 980, + "y": 550, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-7-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-7-actions", + "type": "rectangle", + "x": 1356, + "y": 546, + "width": 24, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-7-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "empty-rows-area", + "type": "rectangle", + "x": 304, + "y": 584, + "width": 1112, + "height": 236, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-bar", + "type": "rectangle", + "x": 304, + "y": 820, + "width": 1112, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-info", + "type": "rectangle", + "x": 328, + "y": 836, + "width": 120, + "height": 16, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-prev", + "type": "rectangle", + "x": 1124, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-1", + "type": "rectangle", + "x": 1164, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-2", + "type": "rectangle", + "x": 1204, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-3", + "type": "rectangle", + "x": 1244, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-ellipsis", + "type": "rectangle", + "x": 1284, + "y": 840, + "width": 20, + "height": 8, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-10", + "type": "rectangle", + "x": 1316, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-next", + "type": "rectangle", + "x": 1356, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/low/settings-billing.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/low/settings-billing.excalidraw new file mode 100644 index 0000000..6868df2 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/low/settings-billing.excalidraw @@ -0,0 +1,1559 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-1", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-2", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-3", + "type": "rectangle", + "x": 20, + "y": 220, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-active", + "type": "rectangle", + "x": 20, + "y": 280, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "rectangle", + "x": 304, + "y": 20, + "width": 120, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-general", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-security", + "type": "rectangle", + "x": 404, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-billing-active", + "type": "rectangle", + "x": 504, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-card", + "type": "rectangle", + "x": 304, + "y": 140, + "width": 540, + "height": 200, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "plan-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-title", + "type": "rectangle", + "x": 324, + "y": 160, + "width": 120, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "plan-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-name-badge", + "type": "rectangle", + "x": 324, + "y": 200, + "width": 60, + "height": 28, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "plan-section" + ], + "roundness": { + "type": 3, + "value": 14 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-price", + "type": "rectangle", + "x": 400, + "y": 200, + "width": 100, + "height": 28, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "plan-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-feature-1", + "type": "rectangle", + "x": 324, + "y": 244, + "width": 200, + "height": 14, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "plan-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-feature-2", + "type": "rectangle", + "x": 324, + "y": 264, + "width": 180, + "height": 14, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "plan-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-feature-3", + "type": "rectangle", + "x": 324, + "y": 284, + "width": 160, + "height": 14, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "plan-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "manage-subscription-button", + "type": "rectangle", + "x": 680, + "y": 180, + "width": 140, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "plan-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-card", + "type": "rectangle", + "x": 864, + "y": 140, + "width": 280, + "height": 200, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "credits-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-title", + "type": "rectangle", + "x": 884, + "y": 160, + "width": 80, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "credits-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-balance", + "type": "rectangle", + "x": 884, + "y": 200, + "width": 120, + "height": 32, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "credits-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-usage-bar-bg", + "type": "rectangle", + "x": 884, + "y": 252, + "width": 240, + "height": 12, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 40, + "groupIds": [ + "credits-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-usage-bar-fill", + "type": "rectangle", + "x": 884, + "y": 252, + "width": 168, + "height": 12, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "credits-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "buy-credits-button", + "type": "rectangle", + "x": 884, + "y": 284, + "width": 120, + "height": 40, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "credits-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "payment-method-card", + "type": "rectangle", + "x": 304, + "y": 360, + "width": 540, + "height": 100, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "payment-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "payment-title", + "type": "rectangle", + "x": 324, + "y": 380, + "width": 140, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "payment-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-icon", + "type": "rectangle", + "x": 324, + "y": 416, + "width": 40, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "payment-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-last-digits", + "type": "rectangle", + "x": 380, + "y": 420, + "width": 100, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "payment-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "update-payment-button", + "type": "rectangle", + "x": 720, + "y": 400, + "width": 100, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "payment-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "billing-history-card", + "type": "rectangle", + "x": 304, + "y": 480, + "width": 840, + "height": 340, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "history-title", + "type": "rectangle", + "x": 324, + "y": 500, + "width": 140, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-header", + "type": "rectangle", + "x": 324, + "y": 540, + "width": 800, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 40, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-date", + "type": "rectangle", + "x": 344, + "y": 552, + "width": 60, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-amount", + "type": "rectangle", + "x": 544, + "y": 552, + "width": 80, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-status", + "type": "rectangle", + "x": 744, + "y": 552, + "width": 60, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invoice", + "type": "rectangle", + "x": 944, + "y": 552, + "width": 80, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1", + "type": "rectangle", + "x": 324, + "y": 580, + "width": 800, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-date", + "type": "rectangle", + "x": 344, + "y": 596, + "width": 100, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-amount", + "type": "rectangle", + "x": 544, + "y": 596, + "width": 60, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status", + "type": "rectangle", + "x": 744, + "y": 592, + "width": 60, + "height": 24, + "strokeColor": "#22c55e", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-invoice", + "type": "rectangle", + "x": 944, + "y": 596, + "width": 80, + "height": 16, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2", + "type": "rectangle", + "x": 324, + "y": 628, + "width": 800, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-date", + "type": "rectangle", + "x": 344, + "y": 644, + "width": 100, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-amount", + "type": "rectangle", + "x": 544, + "y": 644, + "width": 60, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status", + "type": "rectangle", + "x": 744, + "y": 640, + "width": 60, + "height": 24, + "strokeColor": "#22c55e", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-invoice", + "type": "rectangle", + "x": 944, + "y": 644, + "width": 80, + "height": 16, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3", + "type": "rectangle", + "x": 324, + "y": 676, + "width": 800, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-date", + "type": "rectangle", + "x": 344, + "y": 692, + "width": 100, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-amount", + "type": "rectangle", + "x": 544, + "y": 692, + "width": 60, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-status", + "type": "rectangle", + "x": 744, + "y": 688, + "width": 60, + "height": 24, + "strokeColor": "#22c55e", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-invoice", + "type": "rectangle", + "x": 944, + "y": 692, + "width": 80, + "height": 16, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4", + "type": "rectangle", + "x": 324, + "y": 724, + "width": 800, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-date", + "type": "rectangle", + "x": 344, + "y": 740, + "width": 100, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-amount", + "type": "rectangle", + "x": 544, + "y": 740, + "width": 60, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-status", + "type": "rectangle", + "x": 744, + "y": 736, + "width": 60, + "height": 24, + "strokeColor": "#22c55e", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-invoice", + "type": "rectangle", + "x": 944, + "y": 740, + "width": 80, + "height": 16, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/low/settings-general.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/low/settings-general.excalidraw new file mode 100644 index 0000000..51d5ea9 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/low/settings-general.excalidraw @@ -0,0 +1,881 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-1", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-2", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-3", + "type": "rectangle", + "x": 20, + "y": 220, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-active", + "type": "rectangle", + "x": 20, + "y": 280, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "rectangle", + "x": 304, + "y": 20, + "width": 120, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-general-active", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-security", + "type": "rectangle", + "x": 404, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-billing", + "type": "rectangle", + "x": 504, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "profile-card", + "type": "rectangle", + "x": 304, + "y": 140, + "width": 700, + "height": 280, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "profile-card-title", + "type": "rectangle", + "x": 324, + "y": 160, + "width": 100, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "avatar-circle", + "type": "ellipse", + "x": 324, + "y": 200, + "width": 80, + "height": 80, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "avatar-change-button", + "type": "rectangle", + "x": 324, + "y": 300, + "width": 80, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-label", + "type": "rectangle", + "x": 440, + "y": 200, + "width": 60, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "profile-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-input", + "type": "rectangle", + "x": 440, + "y": 220, + "width": 540, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-label", + "type": "rectangle", + "x": 440, + "y": 280, + "width": 60, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "profile-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-input", + "type": "rectangle", + "x": 440, + "y": 300, + "width": 440, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "verified-badge", + "type": "rectangle", + "x": 900, + "y": 308, + "width": 80, + "height": 24, + "strokeColor": "#22c55e", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "save-profile-button", + "type": "rectangle", + "x": 324, + "y": 360, + "width": 100, + "height": 40, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-card", + "type": "rectangle", + "x": 304, + "y": 440, + "width": 700, + "height": 140, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "language-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-card-title", + "type": "rectangle", + "x": 324, + "y": 460, + "width": 100, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "language-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-label", + "type": "rectangle", + "x": 324, + "y": 500, + "width": 80, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "language-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-dropdown", + "type": "rectangle", + "x": 324, + "y": 520, + "width": 300, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "language-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "dropdown-chevron", + "type": "rectangle", + "x": 588, + "y": 532, + "width": 16, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "language-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "danger-zone-card", + "type": "rectangle", + "x": 304, + "y": 600, + "width": 700, + "height": 140, + "strokeColor": "#ef4444", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "danger-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "danger-zone-title", + "type": "rectangle", + "x": 324, + "y": 620, + "width": 120, + "height": 20, + "strokeColor": "#ef4444", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "danger-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "danger-zone-description", + "type": "rectangle", + "x": 324, + "y": 660, + "width": 400, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "danger-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "delete-account-button", + "type": "rectangle", + "x": 324, + "y": 700, + "width": 140, + "height": 40, + "strokeColor": "#ef4444", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "danger-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/low/settings-security.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/low/settings-security.excalidraw new file mode 100644 index 0000000..82d9b49 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/low/settings-security.excalidraw @@ -0,0 +1,1359 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-1", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-2", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-3", + "type": "rectangle", + "x": 20, + "y": 220, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-active", + "type": "rectangle", + "x": 20, + "y": 280, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "rectangle", + "x": 304, + "y": 20, + "width": 120, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-general", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-security-active", + "type": "rectangle", + "x": 404, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-billing", + "type": "rectangle", + "x": 504, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-card", + "type": "rectangle", + "x": 304, + "y": 140, + "width": 700, + "height": 140, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "twofa-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-title", + "type": "rectangle", + "x": 324, + "y": 160, + "width": 200, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "twofa-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-status-indicator", + "type": "rectangle", + "x": 324, + "y": 200, + "width": 80, + "height": 24, + "strokeColor": "#22c55e", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "twofa-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-description", + "type": "rectangle", + "x": 324, + "y": 240, + "width": 500, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "twofa-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-toggle-button", + "type": "rectangle", + "x": 880, + "y": 180, + "width": 100, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "twofa-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkeys-card", + "type": "rectangle", + "x": 304, + "y": 300, + "width": 700, + "height": 220, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "passkeys-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkeys-title", + "type": "rectangle", + "x": 324, + "y": 320, + "width": 100, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "passkeys-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-item-1", + "type": "rectangle", + "x": 324, + "y": 360, + "width": 656, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "passkeys-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-1-name", + "type": "rectangle", + "x": 340, + "y": 372, + "width": 120, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "passkeys-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-1-date", + "type": "rectangle", + "x": 500, + "y": 372, + "width": 100, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "passkeys-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-1-delete", + "type": "rectangle", + "x": 920, + "y": 368, + "width": 48, + "height": 24, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "passkeys-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-item-2", + "type": "rectangle", + "x": 324, + "y": 420, + "width": 656, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "passkeys-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-2-name", + "type": "rectangle", + "x": 340, + "y": 432, + "width": 140, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "passkeys-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-2-date", + "type": "rectangle", + "x": 520, + "y": 432, + "width": 100, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "passkeys-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-2-delete", + "type": "rectangle", + "x": 920, + "y": 428, + "width": 48, + "height": 24, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "passkeys-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "add-passkey-button", + "type": "rectangle", + "x": 324, + "y": 480, + "width": 120, + "height": 40, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "passkeys-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sessions-card", + "type": "rectangle", + "x": 304, + "y": 540, + "width": 700, + "height": 300, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sessions-title", + "type": "rectangle", + "x": 324, + "y": 560, + "width": 140, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-item-1", + "type": "rectangle", + "x": 324, + "y": 600, + "width": 656, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-icon", + "type": "rectangle", + "x": 340, + "y": 612, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-name", + "type": "rectangle", + "x": 380, + "y": 608, + "width": 160, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-location", + "type": "rectangle", + "x": 380, + "y": 628, + "width": 120, + "height": 12, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-current-badge", + "type": "rectangle", + "x": 900, + "y": 612, + "width": 68, + "height": 24, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-item-2", + "type": "rectangle", + "x": 324, + "y": 660, + "width": 656, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-icon", + "type": "rectangle", + "x": 340, + "y": 672, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-name", + "type": "rectangle", + "x": 380, + "y": 668, + "width": 140, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-location", + "type": "rectangle", + "x": 380, + "y": 688, + "width": 100, + "height": 12, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-revoke", + "type": "rectangle", + "x": 908, + "y": 672, + "width": 60, + "height": 24, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-item-3", + "type": "rectangle", + "x": 324, + "y": 720, + "width": 656, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-3-icon", + "type": "rectangle", + "x": 340, + "y": 732, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-3-name", + "type": "rectangle", + "x": 380, + "y": 728, + "width": 180, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-3-location", + "type": "rectangle", + "x": 380, + "y": 748, + "width": 140, + "height": 12, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-3-revoke", + "type": "rectangle", + "x": 908, + "y": 732, + "width": 60, + "height": 24, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-item-4", + "type": "rectangle", + "x": 324, + "y": 780, + "width": 656, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-4-icon", + "type": "rectangle", + "x": 340, + "y": 792, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-4-name", + "type": "rectangle", + "x": 380, + "y": 788, + "width": 120, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-4-location", + "type": "rectangle", + "x": 380, + "y": 808, + "width": 80, + "height": 12, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-4-revoke", + "type": "rectangle", + "x": 908, + "y": 792, + "width": 60, + "height": 24, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/low/sidebar-admin.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/low/sidebar-admin.excalidraw new file mode 100644 index 0000000..efa95c6 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/low/sidebar-admin.excalidraw @@ -0,0 +1,785 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "sidebar-container", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-area", + "type": "rectangle", + "x": 24, + "y": 24, + "width": 160, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 44, + "y": 34, + "width": 80, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Logo", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 14 + }, + { + "id": "admin-badge", + "type": "rectangle", + "x": 196, + "y": 32, + "width": 60, + "height": 24, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "admin-badge-text", + "type": "text", + "x": 206, + "y": 36, + "width": 40, + "height": 16, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Admin", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 10 + }, + { + "id": "section-divider-1", + "type": "line", + "x": 24, + "y": 88, + "width": 232, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 232, + 0 + ] + ] + }, + { + "id": "section-label-admin", + "type": "rectangle", + "x": 24, + "y": 100, + "width": 50, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "sidebar-group", + "section-admin" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-home-active", + "type": "rectangle", + "x": 24, + "y": 120, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-home-icon", + "type": "rectangle", + "x": 36, + "y": 130, + "width": 20, + "height": 20, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-home-text", + "type": "text", + "x": 64, + "y": 130, + "width": 60, + "height": 20, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Home", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-users", + "type": "rectangle", + "x": 24, + "y": 168, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-users-icon", + "type": "rectangle", + "x": 36, + "y": 178, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-users-text", + "type": "text", + "x": 64, + "y": 178, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Users", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-orgs", + "type": "rectangle", + "x": 24, + "y": 216, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-orgs-icon", + "type": "rectangle", + "x": 36, + "y": 226, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-orgs-text", + "type": "text", + "x": 64, + "y": 226, + "width": 100, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Organizations", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-customers", + "type": "rectangle", + "x": 24, + "y": 264, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-customers-icon", + "type": "rectangle", + "x": 36, + "y": 274, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-customers-text", + "type": "text", + "x": 64, + "y": 274, + "width": 80, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Customers", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-2", + "type": "line", + "x": 24, + "y": 324, + "width": 232, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 232, + 0 + ] + ] + }, + { + "id": "nav-back", + "type": "rectangle", + "x": 24, + "y": 344, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-back" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-back-icon", + "type": "rectangle", + "x": 36, + "y": 354, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-back" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-back-text", + "type": "text", + "x": 64, + "y": 354, + "width": 140, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-back" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Back to Dashboard", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-footer", + "type": "line", + "x": 24, + "y": 812, + "width": 232, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 232, + 0 + ] + ] + }, + { + "id": "user-footer", + "type": "rectangle", + "x": 24, + "y": 824, + "width": 232, + "height": 64, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-avatar", + "type": "ellipse", + "x": 24, + "y": 836, + "width": 40, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-name", + "type": "text", + "x": 76, + "y": 838, + "width": 100, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Admin User", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 12 + }, + { + "id": "role-badge", + "type": "rectangle", + "x": 76, + "y": 858, + "width": 60, + "height": 18, + "strokeColor": "#ef4444", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "role-badge-text", + "type": "text", + "x": 86, + "y": 860, + "width": 40, + "height": 14, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Admin", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 9 + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/low/sidebar-apps.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/low/sidebar-apps.excalidraw new file mode 100644 index 0000000..74f9570 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/low/sidebar-apps.excalidraw @@ -0,0 +1,1299 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "sidebar-container", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-area", + "type": "rectangle", + "x": 24, + "y": 24, + "width": 232, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 44, + "y": 34, + "width": 80, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Logo", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 14 + }, + { + "id": "section-divider-1", + "type": "line", + "x": 24, + "y": 88, + "width": 232, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 232, + 0 + ] + ] + }, + { + "id": "section-label-apps", + "type": "rectangle", + "x": 24, + "y": 100, + "width": 40, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "sidebar-group", + "section-apps" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-chat-active", + "type": "rectangle", + "x": 24, + "y": 120, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-chat-icon", + "type": "rectangle", + "x": 36, + "y": 130, + "width": 20, + "height": 20, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-chat-text", + "type": "text", + "x": 64, + "y": 130, + "width": 60, + "height": 20, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Chat", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-image", + "type": "rectangle", + "x": 24, + "y": 168, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-image-icon", + "type": "rectangle", + "x": 36, + "y": 178, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-image-text", + "type": "text", + "x": 64, + "y": 178, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Image", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-tts", + "type": "rectangle", + "x": 24, + "y": 216, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-tts-icon", + "type": "rectangle", + "x": 36, + "y": 226, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-tts-text", + "type": "text", + "x": 64, + "y": 226, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "TTS", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-pdf", + "type": "rectangle", + "x": 24, + "y": 264, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-pdf-icon", + "type": "rectangle", + "x": 36, + "y": 274, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-pdf-text", + "type": "text", + "x": 64, + "y": 274, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "PDF", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-agent", + "type": "rectangle", + "x": 24, + "y": 312, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-agent-icon", + "type": "rectangle", + "x": 36, + "y": 322, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-agent-text", + "type": "text", + "x": 64, + "y": 322, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Agent", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-2", + "type": "line", + "x": 24, + "y": 372, + "width": 232, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 232, + 0 + ] + ] + }, + { + "id": "section-label-free-tools", + "type": "rectangle", + "x": 24, + "y": 384, + "width": 80, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "sidebar-group", + "section-free-tools" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-envin", + "type": "rectangle", + "x": 24, + "y": 404, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-envin-icon", + "type": "rectangle", + "x": 36, + "y": 414, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-envin-text", + "type": "text", + "x": 64, + "y": 414, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Envin", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-ideas", + "type": "rectangle", + "x": 24, + "y": 452, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-ideas-icon", + "type": "rectangle", + "x": 36, + "y": 462, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-ideas-text", + "type": "text", + "x": 64, + "y": 462, + "width": 100, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Ideas Generator", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-extro", + "type": "rectangle", + "x": 24, + "y": 500, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-extro-icon", + "type": "rectangle", + "x": 36, + "y": 510, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-extro-text", + "type": "text", + "x": 64, + "y": 510, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Extro", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-emojai", + "type": "rectangle", + "x": 24, + "y": 548, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-emojai-icon", + "type": "rectangle", + "x": 36, + "y": 558, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-emojai-text", + "type": "text", + "x": 64, + "y": 558, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Emojai", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-3", + "type": "line", + "x": 24, + "y": 608, + "width": 232, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 232, + 0 + ] + ] + }, + { + "id": "section-label-other", + "type": "rectangle", + "x": 24, + "y": 620, + "width": 50, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "sidebar-group", + "section-other" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-home", + "type": "rectangle", + "x": 24, + "y": 640, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-other" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-home-icon", + "type": "rectangle", + "x": 36, + "y": 650, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-other" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-home-text", + "type": "text", + "x": 64, + "y": 650, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-other" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Home", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-docs", + "type": "rectangle", + "x": 24, + "y": 688, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-other" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-docs-icon", + "type": "rectangle", + "x": 36, + "y": 698, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-other" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-docs-text", + "type": "text", + "x": 64, + "y": 698, + "width": 100, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-other" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Documentation", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-blog", + "type": "rectangle", + "x": 24, + "y": 736, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-other" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-blog-icon", + "type": "rectangle", + "x": 36, + "y": 746, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-other" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-blog-text", + "type": "text", + "x": 64, + "y": 746, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-other" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Blog", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-footer", + "type": "line", + "x": 24, + "y": 812, + "width": 232, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 232, + 0 + ] + ] + }, + { + "id": "user-footer", + "type": "rectangle", + "x": 24, + "y": 824, + "width": 232, + "height": 64, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-avatar", + "type": "ellipse", + "x": 24, + "y": 836, + "width": 40, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-name", + "type": "text", + "x": 76, + "y": 846, + "width": 120, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "User Name", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/low/sidebar-dashboard.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/low/sidebar-dashboard.excalidraw new file mode 100644 index 0000000..22f90d4 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/low/sidebar-dashboard.excalidraw @@ -0,0 +1,886 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "sidebar-container", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-area", + "type": "rectangle", + "x": 24, + "y": 24, + "width": 232, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 44, + "y": 34, + "width": 80, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Logo", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 14 + }, + { + "id": "section-divider-1", + "type": "line", + "x": 24, + "y": 88, + "width": 232, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 232, + 0 + ] + ] + }, + { + "id": "section-label-platform", + "type": "rectangle", + "x": 24, + "y": 100, + "width": 70, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "sidebar-group", + "section-platform" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-dashboard-active", + "type": "rectangle", + "x": 24, + "y": 120, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-platform" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-dashboard-icon", + "type": "rectangle", + "x": 36, + "y": 130, + "width": 20, + "height": 20, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-platform" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-dashboard-text", + "type": "text", + "x": 64, + "y": 130, + "width": 80, + "height": 20, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-platform" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Dashboard", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-ai-tools", + "type": "rectangle", + "x": 24, + "y": 168, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-platform" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-ai-tools-icon", + "type": "rectangle", + "x": 36, + "y": 178, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-platform" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-ai-tools-text", + "type": "text", + "x": 64, + "y": 178, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-platform" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "AI Tools", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-2", + "type": "line", + "x": 24, + "y": 228, + "width": 232, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 232, + 0 + ] + ] + }, + { + "id": "section-label-manage", + "type": "rectangle", + "x": 24, + "y": 240, + "width": 60, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "sidebar-group", + "section-manage" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-settings", + "type": "rectangle", + "x": 24, + "y": 260, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-manage" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-settings-icon", + "type": "rectangle", + "x": 36, + "y": 270, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-manage" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-settings-text", + "type": "text", + "x": 64, + "y": 270, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-manage" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Settings", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-3", + "type": "line", + "x": 24, + "y": 320, + "width": 232, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 232, + 0 + ] + ] + }, + { + "id": "section-label-dev", + "type": "rectangle", + "x": 24, + "y": 332, + "width": 30, + "height": 12, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "sidebar-group", + "section-dev" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-demos", + "type": "rectangle", + "x": 24, + "y": 352, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-dev" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-demos-icon", + "type": "rectangle", + "x": 36, + "y": 362, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-dev" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-demos-text", + "type": "text", + "x": 64, + "y": 362, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-dev" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Demos", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-bottom", + "type": "line", + "x": 24, + "y": 700, + "width": 232, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 232, + 0 + ] + ] + }, + { + "id": "nav-support", + "type": "rectangle", + "x": 24, + "y": 712, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-bottom" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-support-icon", + "type": "rectangle", + "x": 36, + "y": 722, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-bottom" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-support-text", + "type": "text", + "x": 64, + "y": 722, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-bottom" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Support", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-feedback", + "type": "rectangle", + "x": 24, + "y": 760, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-bottom" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-feedback-icon", + "type": "rectangle", + "x": 36, + "y": 770, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-bottom" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-feedback-text", + "type": "text", + "x": 64, + "y": 770, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-bottom" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Feedback", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-footer", + "type": "line", + "x": 24, + "y": 812, + "width": 232, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 232, + 0 + ] + ] + }, + { + "id": "user-footer", + "type": "rectangle", + "x": 24, + "y": 824, + "width": 232, + "height": 64, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-avatar", + "type": "ellipse", + "x": 24, + "y": 836, + "width": 40, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-name", + "type": "text", + "x": 76, + "y": 846, + "width": 120, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "User Name", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "dropdown-indicator", + "type": "rectangle", + "x": 224, + "y": 848, + "width": 16, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/medium/auth-forgot-password.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/medium/auth-forgot-password.excalidraw new file mode 100644 index 0000000..d92a2dc --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/medium/auth-forgot-password.excalidraw @@ -0,0 +1,499 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "left-column", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "left-column-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-column", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-column-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-placeholder", + "type": "rectangle", + "x": 300, + "y": 180, + "width": 120, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "logo-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 320, + "y": 190, + "width": 80, + "height": 20, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "logo-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "lock-icon", + "type": "ellipse", + "x": 336, + "y": 260, + "width": 48, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "icon-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "lock-icon-inner", + "type": "rectangle", + "x": 352, + "y": 276, + "width": 16, + "height": 16, + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "icon-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "forgot-password-title-text", + "type": "text", + "x": 200, + "y": 340, + "width": 320, + "height": 32, + "text": "Forgot password?", + "fontSize": 24, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "title-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "forgot-password-description-text", + "type": "text", + "x": 200, + "y": 380, + "width": 320, + "height": 40, + "text": "Enter your email to receive a reset link", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "title-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-label", + "type": "text", + "x": 200, + "y": 440, + "width": 100, + "height": 16, + "text": "Email address", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "email-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-input", + "type": "rectangle", + "x": 200, + "y": 460, + "width": 320, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "email-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-placeholder", + "type": "text", + "x": 212, + "y": 472, + "width": 200, + "height": 20, + "text": "you@example.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "email-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "send-reset-button", + "type": "rectangle", + "x": 200, + "y": 540, + "width": 320, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "button-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "send-reset-button-text", + "type": "text", + "x": 200, + "y": 552, + "width": 320, + "height": 20, + "text": "Send reset link", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "button-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "back-arrow-icon", + "type": "rectangle", + "x": 290, + "y": 620, + "width": 16, + "height": 2, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "back-link-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "back-to-login-link-text", + "type": "text", + "x": 310, + "y": 612, + "width": 120, + "height": 20, + "text": "Back to sign in", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "back-link-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo", + "type": "rectangle", + "x": 1000, + "y": 400, + "width": 160, + "height": 60, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-branding-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo-text", + "type": "text", + "x": 1000, + "y": 420, + "width": 160, + "height": 20, + "text": "MCPGet", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-branding-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-tagline", + "type": "text", + "x": 920, + "y": 480, + "width": 320, + "height": 24, + "text": "Discover and install MCPs with ease", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-column-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/medium/auth-join-org.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/medium/auth-join-org.excalidraw new file mode 100644 index 0000000..8687564 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/medium/auth-join-org.excalidraw @@ -0,0 +1,558 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "left-column", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "left-column-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-column", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-column-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-placeholder", + "type": "rectangle", + "x": 300, + "y": 100, + "width": 120, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "logo-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 320, + "y": 110, + "width": 80, + "height": 20, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "logo-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "join-org-title-text", + "type": "text", + "x": 200, + "y": 180, + "width": 320, + "height": 32, + "text": "Join Organization", + "fontSize": 24, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "title-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "join-org-subtitle-text", + "type": "text", + "x": 200, + "y": 216, + "width": 320, + "height": 20, + "text": "You've been invited to join", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "title-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "invitation-card", + "type": "rectangle", + "x": 180, + "y": 280, + "width": 360, + "height": 220, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "invitation-card-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "org-logo", + "type": "rectangle", + "x": 320, + "y": 300, + "width": 80, + "height": 80, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "invitation-card-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "org-logo-text", + "type": "text", + "x": 320, + "y": 330, + "width": 80, + "height": 20, + "text": "AC", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "invitation-card-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "org-name-text", + "type": "text", + "x": 260, + "y": 400, + "width": 200, + "height": 24, + "text": "Acme Corp", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "invitation-card-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "inviter-name-text", + "type": "text", + "x": 280, + "y": 434, + "width": 160, + "height": 16, + "text": "Invited by John Doe", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "invitation-card-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "role-badge", + "type": "rectangle", + "x": 320, + "y": 460, + "width": 80, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "role-badge-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "role-badge-text", + "type": "text", + "x": 320, + "y": 464, + "width": 80, + "height": 16, + "text": "Member", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "role-badge-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "accept-button", + "type": "rectangle", + "x": 200, + "y": 540, + "width": 320, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "button-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "accept-button-text", + "type": "text", + "x": 200, + "y": 552, + "width": 320, + "height": 20, + "text": "Accept invitation", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "button-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "decline-link-text", + "type": "text", + "x": 320, + "y": 610, + "width": 80, + "height": 20, + "text": "Decline", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "links-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "wrong-account-link-text", + "type": "text", + "x": 260, + "y": 660, + "width": 200, + "height": 20, + "text": "Wrong account?", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "links-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo", + "type": "rectangle", + "x": 1000, + "y": 400, + "width": 160, + "height": 60, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-branding-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo-text", + "type": "text", + "x": 1000, + "y": 420, + "width": 160, + "height": 20, + "text": "MCPGet", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-branding-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-tagline", + "type": "text", + "x": 920, + "y": 480, + "width": 320, + "height": 24, + "text": "Discover and install MCPs with ease", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-column-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/medium/auth-login.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/medium/auth-login.excalidraw new file mode 100644 index 0000000..422ed25 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/medium/auth-login.excalidraw @@ -0,0 +1,813 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "left-column", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "left-column-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-column", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-column-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-placeholder", + "type": "rectangle", + "x": 300, + "y": 80, + "width": 120, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "logo-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 320, + "y": 90, + "width": 80, + "height": 20, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "logo-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-title", + "type": "rectangle", + "x": 200, + "y": 180, + "width": 320, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "title-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-title-text", + "type": "text", + "x": 200, + "y": 180, + "width": 320, + "height": 32, + "text": "Welcome back", + "fontSize": 24, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "title-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-subtitle-text", + "type": "text", + "x": 200, + "y": 216, + "width": 320, + "height": 20, + "text": "Sign in to your account", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "title-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-label", + "type": "text", + "x": 200, + "y": 260, + "width": 100, + "height": 16, + "text": "Email address", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "email-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-input", + "type": "rectangle", + "x": 200, + "y": 280, + "width": 320, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "email-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-placeholder", + "type": "text", + "x": 212, + "y": 292, + "width": 200, + "height": 20, + "text": "you@example.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "email-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-label", + "type": "text", + "x": 200, + "y": 340, + "width": 80, + "height": 16, + "text": "Password", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "password-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-input", + "type": "rectangle", + "x": 200, + "y": 360, + "width": 320, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "password-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-placeholder", + "type": "text", + "x": 212, + "y": 372, + "width": 200, + "height": 20, + "text": "Enter your password", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "password-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-eye-icon", + "type": "ellipse", + "x": 488, + "y": 374, + "width": 16, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "password-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "forgot-password-link", + "type": "text", + "x": 400, + "y": 420, + "width": 120, + "height": 20, + "text": "Forgot password?", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "login-button", + "type": "rectangle", + "x": 200, + "y": 460, + "width": 320, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "button-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "login-button-text", + "type": "text", + "x": 200, + "y": 472, + "width": 320, + "height": 20, + "text": "Sign in", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "button-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-line-left", + "type": "rectangle", + "x": 200, + "y": 534, + "width": 130, + "height": 1, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "divider-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-text", + "type": "text", + "x": 340, + "y": 526, + "width": 40, + "height": 16, + "text": "or", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "divider-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-line-right", + "type": "rectangle", + "x": 390, + "y": 534, + "width": 130, + "height": 1, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "divider-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-google", + "type": "rectangle", + "x": 200, + "y": 560, + "width": 100, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-google-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-google-text", + "type": "text", + "x": 200, + "y": 572, + "width": 100, + "height": 20, + "text": "G", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-google-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-github", + "type": "rectangle", + "x": 310, + "y": 560, + "width": 100, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-github-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-github-text", + "type": "text", + "x": 310, + "y": 572, + "width": 100, + "height": 20, + "text": "GH", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-github-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-apple", + "type": "rectangle", + "x": 420, + "y": 560, + "width": 100, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-apple-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-apple-text", + "type": "text", + "x": 420, + "y": 572, + "width": 100, + "height": 20, + "text": "A", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-apple-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "signup-link-text", + "type": "text", + "x": 200, + "y": 640, + "width": 320, + "height": 20, + "text": "Don't have an account? Sign up", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo", + "type": "rectangle", + "x": 1000, + "y": 400, + "width": 160, + "height": 60, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-branding-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo-text", + "type": "text", + "x": 1000, + "y": 420, + "width": 160, + "height": 20, + "text": "MCPGet", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-branding-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-tagline", + "type": "text", + "x": 920, + "y": 480, + "width": 320, + "height": 24, + "text": "Discover and install MCPs with ease", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-column-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/medium/auth-register.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/medium/auth-register.excalidraw new file mode 100644 index 0000000..6e6f47b --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/medium/auth-register.excalidraw @@ -0,0 +1,949 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "left-column", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "left-column-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-column", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-column-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-placeholder", + "type": "rectangle", + "x": 300, + "y": 40, + "width": 120, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "logo-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 320, + "y": 50, + "width": 80, + "height": 20, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "logo-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "create-account-title-text", + "type": "text", + "x": 200, + "y": 100, + "width": 320, + "height": 32, + "text": "Create account", + "fontSize": 24, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "title-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "create-account-subtitle-text", + "type": "text", + "x": 200, + "y": 136, + "width": 320, + "height": 20, + "text": "Get started for free", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "title-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-label", + "type": "text", + "x": 200, + "y": 180, + "width": 80, + "height": 16, + "text": "Full name", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "name-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-input", + "type": "rectangle", + "x": 200, + "y": 200, + "width": 320, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "name-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-placeholder", + "type": "text", + "x": 212, + "y": 212, + "width": 200, + "height": 20, + "text": "John Doe", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "name-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-label", + "type": "text", + "x": 200, + "y": 260, + "width": 100, + "height": 16, + "text": "Email address", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "email-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-input", + "type": "rectangle", + "x": 200, + "y": 280, + "width": 320, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "email-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-placeholder", + "type": "text", + "x": 212, + "y": 292, + "width": 200, + "height": 20, + "text": "you@example.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "email-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-label", + "type": "text", + "x": 200, + "y": 340, + "width": 80, + "height": 16, + "text": "Password", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "password-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-input", + "type": "rectangle", + "x": 200, + "y": 360, + "width": 320, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "password-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "password-placeholder", + "type": "text", + "x": 212, + "y": 372, + "width": 200, + "height": 20, + "text": "Create a password", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "password-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "confirm-password-label", + "type": "text", + "x": 200, + "y": 420, + "width": 120, + "height": 16, + "text": "Confirm password", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "confirm-password-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "confirm-password-input", + "type": "rectangle", + "x": 200, + "y": 440, + "width": 320, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "confirm-password-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "confirm-password-placeholder", + "type": "text", + "x": 212, + "y": 452, + "width": 200, + "height": 20, + "text": "Confirm your password", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "confirm-password-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "terms-checkbox", + "type": "rectangle", + "x": 200, + "y": 500, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "terms-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "terms-text", + "type": "text", + "x": 230, + "y": 500, + "width": 290, + "height": 20, + "text": "I agree to Terms of Service", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "terms-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "create-account-button", + "type": "rectangle", + "x": 200, + "y": 540, + "width": 320, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "button-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "create-account-button-text", + "type": "text", + "x": 200, + "y": 552, + "width": 320, + "height": 20, + "text": "Create account", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "button-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-line-left", + "type": "rectangle", + "x": 200, + "y": 614, + "width": 130, + "height": 1, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "divider-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-text", + "type": "text", + "x": 340, + "y": 606, + "width": 40, + "height": 16, + "text": "or", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "divider-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-line-right", + "type": "rectangle", + "x": 390, + "y": 614, + "width": 130, + "height": 1, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "divider-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-google", + "type": "rectangle", + "x": 200, + "y": 640, + "width": 100, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-google-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-google-text", + "type": "text", + "x": 200, + "y": 652, + "width": 100, + "height": 20, + "text": "G", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-google-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-github", + "type": "rectangle", + "x": 310, + "y": 640, + "width": 100, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-github-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-github-text", + "type": "text", + "x": 310, + "y": 652, + "width": 100, + "height": 20, + "text": "GH", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-github-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-apple", + "type": "rectangle", + "x": 420, + "y": 640, + "width": 100, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-apple-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-apple-text", + "type": "text", + "x": 420, + "y": 652, + "width": 100, + "height": 20, + "text": "A", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-apple-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "login-link-text", + "type": "text", + "x": 200, + "y": 720, + "width": 320, + "height": 20, + "text": "Already have an account? Sign in", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo", + "type": "rectangle", + "x": 1000, + "y": 400, + "width": 160, + "height": 60, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-branding-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo-text", + "type": "text", + "x": 1000, + "y": 420, + "width": 160, + "height": 20, + "text": "MCPGet", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-branding-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-tagline", + "type": "text", + "x": 920, + "y": 480, + "width": 320, + "height": 24, + "text": "Discover and install MCPs with ease", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#737373", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-column-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/medium/dashboard-admin.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/medium/dashboard-admin.excalidraw new file mode 100644 index 0000000..3c40f30 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/medium/dashboard-admin.excalidraw @@ -0,0 +1,1952 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-text", + "type": "text", + "x": 32, + "y": 24, + "width": 96, + "height": 18, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-home", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-home-icon", + "type": "rectangle", + "x": 32, + "y": 110, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-home-text", + "type": "text", + "x": 64, + "y": 112, + "width": 50, + "height": 16, + "text": "Home", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-icon", + "type": "rectangle", + "x": 32, + "y": 170, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-text", + "type": "text", + "x": 64, + "y": 172, + "width": 50, + "height": 16, + "text": "Users", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-orgs", + "type": "rectangle", + "x": 20, + "y": 220, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-orgs-icon", + "type": "rectangle", + "x": 32, + "y": 230, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-orgs-text", + "type": "text", + "x": 64, + "y": 232, + "width": 100, + "height": 16, + "text": "Organizations", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-customers", + "type": "rectangle", + "x": 20, + "y": 280, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-customers-icon", + "type": "rectangle", + "x": 32, + "y": 290, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-customers-text", + "type": "text", + "x": 64, + "y": 292, + "width": 80, + "height": 16, + "text": "Customers", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-avatar", + "type": "rectangle", + "x": 28, + "y": 858, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-name", + "type": "text", + "x": 56, + "y": 860, + "width": 80, + "height": 14, + "text": "Admin", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "text", + "x": 304, + "y": 20, + "width": 160, + "height": 24, + "text": "Admin Dashboard", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-user-avatar", + "type": "rectangle", + "x": 1388, + "y": 16, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 16 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 264, + "height": 100, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-label", + "type": "text", + "x": 324, + "y": 108, + "width": 80, + "height": 16, + "text": "Total Users", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-value", + "type": "text", + "x": 324, + "y": 136, + "width": 100, + "height": 36, + "text": "1,234", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2", + "type": "rectangle", + "x": 584, + "y": 88, + "width": 264, + "height": 100, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-label", + "type": "text", + "x": 604, + "y": 108, + "width": 80, + "height": 16, + "text": "Organizations", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-value", + "type": "text", + "x": 604, + "y": 136, + "width": 60, + "height": 36, + "text": "56", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3", + "type": "rectangle", + "x": 864, + "y": 88, + "width": 264, + "height": 100, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-label", + "type": "text", + "x": 884, + "y": 108, + "width": 60, + "height": 16, + "text": "Revenue", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-value", + "type": "text", + "x": 884, + "y": 136, + "width": 100, + "height": 36, + "text": "$12.4k", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#22c55e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4", + "type": "rectangle", + "x": 1144, + "y": 88, + "width": 264, + "height": 100, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-label", + "type": "text", + "x": 1164, + "y": 108, + "width": 100, + "height": 16, + "text": "Sessions", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-value", + "type": "text", + "x": 1164, + "y": 136, + "width": 80, + "height": 36, + "text": "342", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-card", + "type": "rectangle", + "x": 304, + "y": 208, + "width": 720, + "height": 440, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-card-title", + "type": "text", + "x": 324, + "y": 228, + "width": 140, + "height": 22, + "text": "Recent Activity", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-1", + "type": "rectangle", + "x": 324, + "y": 268, + "width": 680, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-1-avatar", + "type": "rectangle", + "x": 340, + "y": 280, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-1-text", + "type": "text", + "x": 376, + "y": 284, + "width": 300, + "height": 16, + "text": "User signed up - john@example.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-1-time", + "type": "text", + "x": 924, + "y": 284, + "width": 60, + "height": 16, + "text": "2m ago", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-2", + "type": "rectangle", + "x": 324, + "y": 328, + "width": 680, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-2-avatar", + "type": "rectangle", + "x": 340, + "y": 340, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-2-text", + "type": "text", + "x": 376, + "y": 344, + "width": 280, + "height": 16, + "text": "New org created - Acme Corp", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-2-time", + "type": "text", + "x": 924, + "y": 344, + "width": 60, + "height": 16, + "text": "5m ago", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-3", + "type": "rectangle", + "x": 324, + "y": 388, + "width": 680, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-3-avatar", + "type": "rectangle", + "x": 340, + "y": 400, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-3-text", + "type": "text", + "x": 376, + "y": 404, + "width": 320, + "height": 16, + "text": "Subscription upgraded - Pro Plan", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-3-time", + "type": "text", + "x": 924, + "y": 404, + "width": 60, + "height": 16, + "text": "12m ago", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-4", + "type": "rectangle", + "x": 324, + "y": 448, + "width": 680, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-4-avatar", + "type": "rectangle", + "x": 340, + "y": 460, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-4-text", + "type": "text", + "x": 376, + "y": 464, + "width": 260, + "height": 16, + "text": "User invited - team@acme.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-4-time", + "type": "text", + "x": 924, + "y": 464, + "width": 60, + "height": 16, + "text": "25m ago", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-5", + "type": "rectangle", + "x": 324, + "y": 508, + "width": 680, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-5-avatar", + "type": "rectangle", + "x": 340, + "y": 520, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-5-text", + "type": "text", + "x": 376, + "y": 524, + "width": 340, + "height": 16, + "text": "API rate limit exceeded - user_123", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-5-time", + "type": "text", + "x": 924, + "y": 524, + "width": 60, + "height": 16, + "text": "1h ago", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-6", + "type": "rectangle", + "x": 324, + "y": 568, + "width": 680, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-6-avatar", + "type": "rectangle", + "x": 340, + "y": 580, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-6-text", + "type": "text", + "x": 376, + "y": 584, + "width": 290, + "height": 16, + "text": "New feature deployed - v2.1.0", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "activity-row-6-time", + "type": "text", + "x": 924, + "y": 584, + "width": 60, + "height": 16, + "text": "2h ago", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "right", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-actions-card", + "type": "rectangle", + "x": 1044, + "y": 208, + "width": 364, + "height": 440, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-actions-title", + "type": "text", + "x": 1064, + "y": 228, + "width": 120, + "height": 22, + "text": "Quick Actions", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-1", + "type": "rectangle", + "x": 1064, + "y": 268, + "width": 324, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-1-text", + "type": "text", + "x": 1184, + "y": 284, + "width": 84, + "height": 16, + "text": "Add User", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-2", + "type": "rectangle", + "x": 1064, + "y": 332, + "width": 324, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-2-text", + "type": "text", + "x": 1172, + "y": 348, + "width": 108, + "height": 16, + "text": "View Reports", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-3", + "type": "rectangle", + "x": 1064, + "y": 396, + "width": 324, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-3-text", + "type": "text", + "x": 1188, + "y": 412, + "width": 76, + "height": 16, + "text": "Settings", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-4", + "type": "rectangle", + "x": 1064, + "y": 460, + "width": 324, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-4-text", + "type": "text", + "x": 1176, + "y": 476, + "width": 100, + "height": 16, + "text": "Audit Logs", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-5", + "type": "rectangle", + "x": 1064, + "y": 524, + "width": 324, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "quick-action-btn-5-text", + "type": "text", + "x": 1156, + "y": 540, + "width": 140, + "height": 16, + "text": "System Maintenance", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/medium/dashboard-org.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/medium/dashboard-org.excalidraw new file mode 100644 index 0000000..b9864d2 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/medium/dashboard-org.excalidraw @@ -0,0 +1,1813 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-text", + "type": "text", + "x": 32, + "y": 24, + "width": 96, + "height": 18, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-overview", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-overview-icon", + "type": "rectangle", + "x": 32, + "y": 110, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-overview-text", + "type": "text", + "x": 64, + "y": 112, + "width": 80, + "height": 16, + "text": "Overview", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-icon", + "type": "rectangle", + "x": 32, + "y": 170, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-text", + "type": "text", + "x": 64, + "y": 172, + "width": 70, + "height": 16, + "text": "Members", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings", + "type": "rectangle", + "x": 20, + "y": 220, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-icon", + "type": "rectangle", + "x": 32, + "y": 230, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-text", + "type": "text", + "x": 64, + "y": 232, + "width": 60, + "height": 16, + "text": "Settings", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing", + "type": "rectangle", + "x": 20, + "y": 280, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing-icon", + "type": "rectangle", + "x": 32, + "y": 290, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing-text", + "type": "text", + "x": 64, + "y": 292, + "width": 50, + "height": 16, + "text": "Billing", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-avatar", + "type": "rectangle", + "x": 28, + "y": 858, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-name", + "type": "text", + "x": 56, + "y": 860, + "width": 80, + "height": 14, + "text": "Acme Inc.", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "text", + "x": 304, + "y": 20, + "width": 200, + "height": 24, + "text": "Organization Overview", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-user-avatar", + "type": "rectangle", + "x": 1388, + "y": 16, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 16 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 264, + "height": 100, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-label", + "type": "text", + "x": 324, + "y": 108, + "width": 100, + "height": 16, + "text": "Total Members", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-1-value", + "type": "text", + "x": 324, + "y": 136, + "width": 80, + "height": 36, + "text": "24", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2", + "type": "rectangle", + "x": 584, + "y": 88, + "width": 264, + "height": 100, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-label", + "type": "text", + "x": 604, + "y": 108, + "width": 100, + "height": 16, + "text": "Active Users", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-2-value", + "type": "text", + "x": 604, + "y": 136, + "width": 60, + "height": 36, + "text": "18", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3", + "type": "rectangle", + "x": 864, + "y": 88, + "width": 264, + "height": 100, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-label", + "type": "text", + "x": 884, + "y": 108, + "width": 80, + "height": 16, + "text": "API Calls", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-3-value", + "type": "text", + "x": 884, + "y": 136, + "width": 100, + "height": 36, + "text": "12.5k", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4", + "type": "rectangle", + "x": 1144, + "y": 88, + "width": 264, + "height": 100, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-label", + "type": "text", + "x": 1164, + "y": 108, + "width": 100, + "height": 16, + "text": "Storage", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "stat-card-4-value", + "type": "text", + "x": 1164, + "y": 136, + "width": 80, + "height": 36, + "text": "2.4 GB", + "fontSize": 32, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-card", + "type": "rectangle", + "x": 304, + "y": 208, + "width": 1104, + "height": 320, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-title", + "type": "text", + "x": 324, + "y": 228, + "width": 140, + "height": 22, + "text": "Usage Over Time", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-area", + "type": "rectangle", + "x": 324, + "y": 268, + "width": 1064, + "height": 240, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-line-1", + "type": "rectangle", + "x": 324, + "y": 380, + "width": 1064, + "height": 4, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-line-2", + "type": "rectangle", + "x": 324, + "y": 340, + "width": 1064, + "height": 4, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-line-3", + "type": "rectangle", + "x": 324, + "y": 420, + "width": 1064, + "height": 4, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-line-4", + "type": "rectangle", + "x": 324, + "y": 460, + "width": 1064, + "height": 4, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-axis-jan", + "type": "text", + "x": 364, + "y": 488, + "width": 30, + "height": 12, + "text": "Jan", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-axis-feb", + "type": "text", + "x": 544, + "y": 488, + "width": 30, + "height": 12, + "text": "Feb", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-axis-mar", + "type": "text", + "x": 724, + "y": 488, + "width": 30, + "height": 12, + "text": "Mar", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-axis-apr", + "type": "text", + "x": 904, + "y": 488, + "width": 30, + "height": 12, + "text": "Apr", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-axis-may", + "type": "text", + "x": 1084, + "y": 488, + "width": 30, + "height": 12, + "text": "May", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "main-chart-axis-jun", + "type": "text", + "x": 1264, + "y": 488, + "width": 30, + "height": 12, + "text": "Jun", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1", + "type": "rectangle", + "x": 304, + "y": 548, + "width": 540, + "height": 260, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-title", + "type": "text", + "x": 324, + "y": 568, + "width": 100, + "height": 22, + "text": "By Category", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-area", + "type": "rectangle", + "x": 324, + "y": 608, + "width": 500, + "height": 180, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-1", + "type": "rectangle", + "x": 364, + "y": 688, + "width": 40, + "height": 80, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-2", + "type": "rectangle", + "x": 444, + "y": 648, + "width": 40, + "height": 120, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-3", + "type": "rectangle", + "x": 524, + "y": 708, + "width": 40, + "height": 60, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-4", + "type": "rectangle", + "x": 604, + "y": 628, + "width": 40, + "height": 140, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-5", + "type": "rectangle", + "x": 684, + "y": 668, + "width": 40, + "height": 100, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-1-bar-6", + "type": "rectangle", + "x": 764, + "y": 698, + "width": 40, + "height": 70, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2", + "type": "rectangle", + "x": 864, + "y": 548, + "width": 544, + "height": 260, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-title", + "type": "text", + "x": 884, + "y": 568, + "width": 120, + "height": 22, + "text": "Distribution", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-area", + "type": "rectangle", + "x": 884, + "y": 608, + "width": 504, + "height": 180, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-pie", + "type": "rectangle", + "x": 1036, + "y": 628, + "width": 140, + "height": 140, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 70 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-pie-inner", + "type": "rectangle", + "x": 1071, + "y": 663, + "width": 70, + "height": 70, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 35 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-legend-1", + "type": "rectangle", + "x": 1220, + "y": 648, + "width": 12, + "height": 12, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-legend-1-text", + "type": "text", + "x": 1240, + "y": 648, + "width": 60, + "height": 12, + "text": "Chat 45%", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-legend-2", + "type": "rectangle", + "x": 1220, + "y": 672, + "width": 12, + "height": 12, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-legend-2-text", + "type": "text", + "x": 1240, + "y": 672, + "width": 70, + "height": 12, + "text": "Image 35%", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-legend-3", + "type": "rectangle", + "x": 1220, + "y": 696, + "width": 12, + "height": 12, + "strokeColor": "#e5e5e5", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-chart-2-legend-3-text", + "type": "text", + "x": 1240, + "y": 696, + "width": 60, + "height": 12, + "text": "PDF 20%", + "fontSize": 10, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/medium/dashboard-user.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/medium/dashboard-user.excalidraw new file mode 100644 index 0000000..e85a56c --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/medium/dashboard-user.excalidraw @@ -0,0 +1,1380 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-text", + "type": "text", + "x": 32, + "y": 24, + "width": 96, + "height": 18, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-icon", + "type": "rectangle", + "x": 32, + "y": 110, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-text", + "type": "text", + "x": 64, + "y": 112, + "width": 80, + "height": 16, + "text": "Dashboard", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-aitools", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-aitools-icon", + "type": "rectangle", + "x": 32, + "y": 170, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-aitools-text", + "type": "text", + "x": 64, + "y": 172, + "width": 60, + "height": 16, + "text": "AI Tools", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings", + "type": "rectangle", + "x": 20, + "y": 220, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-icon", + "type": "rectangle", + "x": 32, + "y": 230, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-text", + "type": "text", + "x": 64, + "y": 232, + "width": 60, + "height": 16, + "text": "Settings", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-support-item", + "type": "rectangle", + "x": 20, + "y": 720, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-support-icon", + "type": "rectangle", + "x": 32, + "y": 730, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-support-text", + "type": "text", + "x": 64, + "y": 732, + "width": 60, + "height": 16, + "text": "Support", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-feedback-item", + "type": "rectangle", + "x": 20, + "y": 780, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-feedback-icon", + "type": "rectangle", + "x": 32, + "y": 790, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-feedback-text", + "type": "text", + "x": 64, + "y": 792, + "width": 70, + "height": 16, + "text": "Feedback", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-avatar", + "type": "rectangle", + "x": 28, + "y": 858, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-name", + "type": "text", + "x": 56, + "y": 860, + "width": 80, + "height": 14, + "text": "John Doe", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "text", + "x": 304, + "y": 20, + "width": 120, + "height": 24, + "text": "Dashboard", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-user-avatar", + "type": "rectangle", + "x": 1388, + "y": 16, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 16 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-card", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 1112, + "height": 120, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-title", + "type": "text", + "x": 324, + "y": 108, + "width": 200, + "height": 28, + "text": "Welcome back!", + "fontSize": 24, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-subtitle", + "type": "text", + "x": 324, + "y": 148, + "width": 400, + "height": 18, + "text": "Get started with our AI tools", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "welcome-subtitle-2", + "type": "text", + "x": 324, + "y": 172, + "width": 300, + "height": 14, + "text": "Explore chat, image generation, and PDF tools below.", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1", + "type": "rectangle", + "x": 304, + "y": 228, + "width": 360, + "height": 220, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-icon", + "type": "rectangle", + "x": 324, + "y": 248, + "width": 48, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-title", + "type": "text", + "x": 324, + "y": 316, + "width": 100, + "height": 22, + "text": "AI Chat", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-desc", + "type": "text", + "x": 324, + "y": 348, + "width": 320, + "height": 18, + "text": "Start conversations with AI", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-desc-2", + "type": "text", + "x": 324, + "y": 372, + "width": 280, + "height": 14, + "text": "Ask questions, get help, brainstorm ideas.", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-button", + "type": "rectangle", + "x": 324, + "y": 404, + "width": 100, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-1-button-text", + "type": "text", + "x": 352, + "y": 414, + "width": 44, + "height": 16, + "text": "Open", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2", + "type": "rectangle", + "x": 680, + "y": 228, + "width": 360, + "height": 220, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-icon", + "type": "rectangle", + "x": 700, + "y": 248, + "width": 48, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-title", + "type": "text", + "x": 700, + "y": 316, + "width": 160, + "height": 22, + "text": "Image Generation", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-desc", + "type": "text", + "x": 700, + "y": 348, + "width": 320, + "height": 18, + "text": "Create images from text", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-desc-2", + "type": "text", + "x": 700, + "y": 372, + "width": 260, + "height": 14, + "text": "Generate stunning visuals with AI.", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-button", + "type": "rectangle", + "x": 700, + "y": 404, + "width": 100, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-2-button-text", + "type": "text", + "x": 720, + "y": 414, + "width": 60, + "height": 16, + "text": "Try Now", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3", + "type": "rectangle", + "x": 1056, + "y": 228, + "width": 360, + "height": 220, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-icon", + "type": "rectangle", + "x": 1076, + "y": 248, + "width": 48, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-title", + "type": "text", + "x": 1076, + "y": 316, + "width": 100, + "height": 22, + "text": "PDF Tools", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-desc", + "type": "text", + "x": 1076, + "y": 348, + "width": 320, + "height": 18, + "text": "Chat with your documents", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-desc-2", + "type": "text", + "x": 1076, + "y": 372, + "width": 280, + "height": 14, + "text": "Upload PDFs and ask questions about them.", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-button", + "type": "rectangle", + "x": 1076, + "y": 404, + "width": 100, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "feature-card-3-button-text", + "type": "text", + "x": 1100, + "y": 414, + "width": 52, + "height": 16, + "text": "Upload", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/medium/data-table-invitations.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/medium/data-table-invitations.excalidraw new file mode 100644 index 0000000..07f1687 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/medium/data-table-invitations.excalidraw @@ -0,0 +1,2067 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-switcher", + "type": "rectangle", + "x": 20, + "y": 80, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-avatar", + "type": "rectangle", + "x": 32, + "y": 88, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-name", + "type": "text", + "x": 64, + "y": 92, + "width": 100, + "height": 16, + "text": "Acme Inc", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-section-label", + "type": "text", + "x": 20, + "y": 140, + "width": 80, + "height": 12, + "text": "WORKSPACE", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-label", + "type": "text", + "x": 60, + "y": 172, + "width": 80, + "height": 16, + "text": "Dashboard", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-active", + "type": "rectangle", + "x": 20, + "y": 208, + "width": 240, + "height": 40, + "strokeColor": "#f97316", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-icon", + "type": "rectangle", + "x": 32, + "y": 218, + "width": 20, + "height": 20, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-label", + "type": "text", + "x": 60, + "y": 220, + "width": 60, + "height": 16, + "text": "Members", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings", + "type": "rectangle", + "x": 20, + "y": 256, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-label", + "type": "text", + "x": 60, + "y": 268, + "width": 60, + "height": 16, + "text": "Settings", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing", + "type": "rectangle", + "x": 20, + "y": 304, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing-label", + "type": "text", + "x": 60, + "y": 316, + "width": 50, + "height": 16, + "text": "Billing", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "text", + "x": 304, + "y": 20, + "width": 80, + "height": 24, + "text": "Members", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invite-button", + "type": "rectangle", + "x": 1276, + "y": 16, + "width": 140, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invite-text", + "type": "text", + "x": 1296, + "y": 24, + "width": 100, + "height": 16, + "text": "Invite Member", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tabs-container", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 1112, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-members", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 120, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-members-label", + "type": "text", + "x": 316, + "y": 104, + "width": 96, + "height": 16, + "text": "Members (24)", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "tabs-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-invitations-active", + "type": "rectangle", + "x": 424, + "y": 88, + "width": 180, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-invitations-label", + "type": "text", + "x": 436, + "y": 104, + "width": 156, + "height": 16, + "text": "Pending Invitations (3)", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-invitations-underline", + "type": "rectangle", + "x": 424, + "y": 132, + "width": 180, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-container", + "type": "rectangle", + "x": 304, + "y": 152, + "width": 1112, + "height": 716, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "toolbar", + "type": "rectangle", + "x": 304, + "y": 152, + "width": 1112, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-input", + "type": "rectangle", + "x": 316, + "y": 162, + "width": 240, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-placeholder", + "type": "text", + "x": 336, + "y": 172, + "width": 160, + "height": 16, + "text": "Search invitations...", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-status", + "type": "rectangle", + "x": 568, + "y": 162, + "width": 100, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-status-text", + "type": "text", + "x": 588, + "y": 172, + "width": 50, + "height": 16, + "text": "Status", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-status-arrow", + "type": "rectangle", + "x": 648, + "y": 176, + "width": 8, + "height": 8, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-header-row", + "type": "rectangle", + "x": 304, + "y": 208, + "width": 1112, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-email-col", + "type": "text", + "x": 320, + "y": 224, + "width": 50, + "height": 14, + "text": "Email", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-role-col", + "type": "text", + "x": 480, + "y": 224, + "width": 40, + "height": 14, + "text": "Role", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invitedby-col", + "type": "text", + "x": 600, + "y": 224, + "width": 70, + "height": 14, + "text": "Invited by", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-sent-col", + "type": "text", + "x": 740, + "y": 224, + "width": 40, + "height": 14, + "text": "Sent", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-expires-col", + "type": "text", + "x": 860, + "y": 224, + "width": 55, + "height": 14, + "text": "Expires", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-status-col", + "type": "text", + "x": 980, + "y": 224, + "width": 50, + "height": 14, + "text": "Status", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-actions-col", + "type": "text", + "x": 1120, + "y": 224, + "width": 55, + "height": 14, + "text": "Actions", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-1", + "type": "rectangle", + "x": 304, + "y": 256, + "width": 1112, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-email", + "type": "text", + "x": 320, + "y": 277, + "width": 140, + "height": 14, + "text": "alice@example.com", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-badge", + "type": "rectangle", + "x": 480, + "y": 274, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-text", + "type": "text", + "x": 490, + "y": 277, + "width": 45, + "height": 14, + "text": "Member", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-invitedby", + "type": "text", + "x": 600, + "y": 277, + "width": 80, + "height": 14, + "text": "John Doe", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-sent", + "type": "text", + "x": 740, + "y": 277, + "width": 50, + "height": 14, + "text": "Jan 20", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-expires", + "type": "text", + "x": 860, + "y": 277, + "width": 50, + "height": 14, + "text": "Jan 27", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status-badge", + "type": "rectangle", + "x": 980, + "y": 274, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status-text", + "type": "text", + "x": 990, + "y": 277, + "width": 45, + "height": 14, + "text": "Pending", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-resend", + "type": "rectangle", + "x": 1120, + "y": 274, + "width": 60, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-resend-text", + "type": "text", + "x": 1130, + "y": 277, + "width": 40, + "height": 14, + "text": "Resend", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-cancel", + "type": "rectangle", + "x": 1188, + "y": 274, + "width": 60, + "height": 20, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-action-cancel-text", + "type": "text", + "x": 1198, + "y": 277, + "width": 40, + "height": 14, + "text": "Cancel", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-2", + "type": "rectangle", + "x": 304, + "y": 312, + "width": 1112, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-email", + "type": "text", + "x": 320, + "y": 333, + "width": 150, + "height": 14, + "text": "charlie@example.com", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-badge", + "type": "rectangle", + "x": 480, + "y": 330, + "width": 55, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-text", + "type": "text", + "x": 492, + "y": 333, + "width": 35, + "height": 14, + "text": "Admin", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-invitedby", + "type": "text", + "x": 600, + "y": 333, + "width": 80, + "height": 14, + "text": "Jane Smith", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-sent", + "type": "text", + "x": 740, + "y": 333, + "width": 50, + "height": 14, + "text": "Jan 15", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-expires", + "type": "text", + "x": 860, + "y": 333, + "width": 50, + "height": 14, + "text": "Jan 22", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status-badge", + "type": "rectangle", + "x": 980, + "y": 330, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status-text", + "type": "text", + "x": 990, + "y": 333, + "width": 45, + "height": 14, + "text": "Expired", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-resend", + "type": "rectangle", + "x": 1120, + "y": 330, + "width": 60, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-resend-text", + "type": "text", + "x": 1130, + "y": 333, + "width": 40, + "height": 14, + "text": "Resend", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-cancel", + "type": "rectangle", + "x": 1188, + "y": 330, + "width": 60, + "height": 20, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-action-cancel-text", + "type": "text", + "x": 1198, + "y": 333, + "width": 40, + "height": 14, + "text": "Cancel", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "empty-rows-area", + "type": "rectangle", + "x": 304, + "y": 368, + "width": 1112, + "height": 452, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-bar", + "type": "rectangle", + "x": 304, + "y": 820, + "width": 1112, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-info", + "type": "text", + "x": 328, + "y": 836, + "width": 110, + "height": 16, + "text": "Showing 1-3 of 3", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-prev", + "type": "rectangle", + "x": 1284, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-1", + "type": "rectangle", + "x": 1324, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-1-text", + "type": "text", + "x": 1336, + "y": 838, + "width": 10, + "height": 12, + "text": "1", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-next", + "type": "rectangle", + "x": 1364, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/medium/data-table-members.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/medium/data-table-members.excalidraw new file mode 100644 index 0000000..26a10d2 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/medium/data-table-members.excalidraw @@ -0,0 +1,2052 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-switcher", + "type": "rectangle", + "x": 20, + "y": 80, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-avatar", + "type": "rectangle", + "x": 32, + "y": 88, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-org-name", + "type": "text", + "x": 64, + "y": 92, + "width": 100, + "height": 16, + "text": "Acme Inc", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-section-label", + "type": "text", + "x": 20, + "y": 140, + "width": 80, + "height": 12, + "text": "WORKSPACE", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 50, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-label", + "type": "text", + "x": 60, + "y": 172, + "width": 80, + "height": 16, + "text": "Dashboard", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-active", + "type": "rectangle", + "x": 20, + "y": 208, + "width": 240, + "height": 40, + "strokeColor": "#f97316", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-icon", + "type": "rectangle", + "x": 32, + "y": 218, + "width": 20, + "height": 20, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-members-label", + "type": "text", + "x": 60, + "y": 220, + "width": 60, + "height": 16, + "text": "Members", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings", + "type": "rectangle", + "x": 20, + "y": 256, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-label", + "type": "text", + "x": 60, + "y": 268, + "width": 60, + "height": 16, + "text": "Settings", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing", + "type": "rectangle", + "x": 20, + "y": 304, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-billing-label", + "type": "text", + "x": 60, + "y": 316, + "width": 50, + "height": 16, + "text": "Billing", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "text", + "x": 304, + "y": 20, + "width": 80, + "height": 24, + "text": "Members", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invite-button", + "type": "rectangle", + "x": 1276, + "y": 16, + "width": 140, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invite-text", + "type": "text", + "x": 1296, + "y": 24, + "width": 100, + "height": 16, + "text": "Invite Member", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tabs-container", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 1112, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-members-active", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 120, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-members-label", + "type": "text", + "x": 316, + "y": 104, + "width": 96, + "height": 16, + "text": "Members (24)", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-members-underline", + "type": "rectangle", + "x": 304, + "y": 132, + "width": 120, + "height": 4, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-invitations", + "type": "rectangle", + "x": 424, + "y": 88, + "width": 180, + "height": 48, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-invitations-label", + "type": "text", + "x": 436, + "y": 104, + "width": 156, + "height": 16, + "text": "Pending Invitations (3)", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "tabs-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-container", + "type": "rectangle", + "x": 304, + "y": 152, + "width": 1112, + "height": 716, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "toolbar", + "type": "rectangle", + "x": 304, + "y": 152, + "width": 1112, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-input", + "type": "rectangle", + "x": 316, + "y": 162, + "width": 240, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-placeholder", + "type": "text", + "x": 336, + "y": 172, + "width": 140, + "height": 16, + "text": "Search members...", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role", + "type": "rectangle", + "x": 568, + "y": 162, + "width": 100, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role-text", + "type": "text", + "x": 588, + "y": 172, + "width": 40, + "height": 16, + "text": "Role", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role-arrow", + "type": "rectangle", + "x": 648, + "y": 176, + "width": 8, + "height": 8, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-header-row", + "type": "rectangle", + "x": 304, + "y": 208, + "width": 1112, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-member-col", + "type": "text", + "x": 320, + "y": 224, + "width": 60, + "height": 14, + "text": "Member", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-role-col", + "type": "text", + "x": 600, + "y": 224, + "width": 40, + "height": 14, + "text": "Role", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-joined-col", + "type": "text", + "x": 880, + "y": 224, + "width": 50, + "height": 14, + "text": "Joined", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-actions-col", + "type": "text", + "x": 1340, + "y": 224, + "width": 55, + "height": 14, + "text": "Actions", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-1", + "type": "rectangle", + "x": 304, + "y": 256, + "width": 1112, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-avatar", + "type": "rectangle", + "x": 320, + "y": 274, + "width": 28, + "height": 28, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 14 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-name", + "type": "text", + "x": 360, + "y": 270, + "width": 80, + "height": 14, + "text": "John Doe", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-email", + "type": "text", + "x": 360, + "y": 286, + "width": 130, + "height": 12, + "text": "john@example.com", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-dropdown", + "type": "rectangle", + "x": 600, + "y": 276, + "width": 100, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-text", + "type": "text", + "x": 612, + "y": 281, + "width": 50, + "height": 14, + "text": "Owner", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-arrow", + "type": "rectangle", + "x": 680, + "y": 284, + "width": 8, + "height": 8, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-joined", + "type": "text", + "x": 880, + "y": 281, + "width": 90, + "height": 14, + "text": "Jan 1, 2024", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-remove", + "type": "rectangle", + "x": 1356, + "y": 278, + "width": 24, + "height": 20, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-2", + "type": "rectangle", + "x": 304, + "y": 320, + "width": 1112, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-avatar", + "type": "rectangle", + "x": 320, + "y": 338, + "width": 28, + "height": 28, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 14 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-name", + "type": "text", + "x": 360, + "y": 334, + "width": 90, + "height": 14, + "text": "Jane Smith", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-email", + "type": "text", + "x": 360, + "y": 350, + "width": 130, + "height": 12, + "text": "jane@example.com", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-dropdown", + "type": "rectangle", + "x": 600, + "y": 340, + "width": 100, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-text", + "type": "text", + "x": 612, + "y": 345, + "width": 50, + "height": 14, + "text": "Admin", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-arrow", + "type": "rectangle", + "x": 680, + "y": 348, + "width": 8, + "height": 8, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-joined", + "type": "text", + "x": 880, + "y": 345, + "width": 90, + "height": 14, + "text": "Jan 5, 2024", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-remove", + "type": "rectangle", + "x": 1356, + "y": 342, + "width": 24, + "height": 20, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-3", + "type": "rectangle", + "x": 304, + "y": 384, + "width": 1112, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-avatar", + "type": "rectangle", + "x": 320, + "y": 402, + "width": 28, + "height": 28, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 14 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-name", + "type": "text", + "x": 360, + "y": 398, + "width": 90, + "height": 14, + "text": "Bob Wilson", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-email", + "type": "text", + "x": 360, + "y": 414, + "width": 130, + "height": 12, + "text": "bob@example.com", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-dropdown", + "type": "rectangle", + "x": 600, + "y": 404, + "width": 100, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-text", + "type": "text", + "x": 612, + "y": 409, + "width": 50, + "height": 14, + "text": "Member", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-arrow", + "type": "rectangle", + "x": 680, + "y": 412, + "width": 8, + "height": 8, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-joined", + "type": "text", + "x": 880, + "y": 409, + "width": 95, + "height": 14, + "text": "Jan 10, 2024", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-remove", + "type": "rectangle", + "x": 1356, + "y": 406, + "width": 24, + "height": 20, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "empty-rows-area", + "type": "rectangle", + "x": 304, + "y": 448, + "width": 1112, + "height": 372, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-bar", + "type": "rectangle", + "x": 304, + "y": 820, + "width": 1112, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-info", + "type": "text", + "x": 328, + "y": 836, + "width": 130, + "height": 16, + "text": "Showing 1-10 of 24", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-prev", + "type": "rectangle", + "x": 1244, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-1", + "type": "rectangle", + "x": 1284, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-1-text", + "type": "text", + "x": 1296, + "y": 838, + "width": 10, + "height": 12, + "text": "1", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-2", + "type": "rectangle", + "x": 1324, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-2-text", + "type": "text", + "x": 1336, + "y": 838, + "width": 10, + "height": 12, + "text": "2", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-next", + "type": "rectangle", + "x": 1364, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/medium/data-table-users.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/medium/data-table-users.excalidraw new file mode 100644 index 0000000..96e1537 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/medium/data-table-users.excalidraw @@ -0,0 +1,2475 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-section-label", + "type": "text", + "x": 20, + "y": 80, + "width": 80, + "height": 14, + "text": "ADMIN", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-dashboard-label", + "type": "text", + "x": 60, + "y": 112, + "width": 80, + "height": 16, + "text": "Dashboard", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-active", + "type": "rectangle", + "x": 20, + "y": 148, + "width": 240, + "height": 40, + "strokeColor": "#f97316", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-icon", + "type": "rectangle", + "x": 32, + "y": 158, + "width": 20, + "height": 20, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-users-label", + "type": "text", + "x": 60, + "y": 160, + "width": 50, + "height": 16, + "text": "Users", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings", + "type": "rectangle", + "x": 20, + "y": 196, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-label", + "type": "text", + "x": 60, + "y": 208, + "width": 60, + "height": 16, + "text": "Settings", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-analytics", + "type": "rectangle", + "x": 20, + "y": 244, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-analytics-label", + "type": "text", + "x": 60, + "y": 256, + "width": 70, + "height": 16, + "text": "Analytics", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "text", + "x": 304, + "y": 20, + "width": 60, + "height": 24, + "text": "Users", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-count-badge", + "type": "rectangle", + "x": 372, + "y": 20, + "width": 80, + "height": 24, + "strokeColor": "transparent", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-count-text", + "type": "text", + "x": 382, + "y": 24, + "width": 60, + "height": 16, + "text": "1,234 users", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-container", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 1112, + "height": 780, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "toolbar", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 1112, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-input", + "type": "rectangle", + "x": 316, + "y": 98, + "width": 200, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-placeholder", + "type": "text", + "x": 336, + "y": 108, + "width": 120, + "height": 16, + "text": "Search users...", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role", + "type": "rectangle", + "x": 528, + "y": 98, + "width": 80, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-role-text", + "type": "text", + "x": 540, + "y": 108, + "width": 40, + "height": 16, + "text": "Role", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-2fa", + "type": "rectangle", + "x": 620, + "y": 98, + "width": 70, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-2fa-text", + "type": "text", + "x": 635, + "y": 108, + "width": 30, + "height": 16, + "text": "2FA", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-status", + "type": "rectangle", + "x": 702, + "y": 98, + "width": 80, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-status-text", + "type": "text", + "x": 714, + "y": 108, + "width": 50, + "height": 16, + "text": "Status", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-date", + "type": "rectangle", + "x": 794, + "y": 98, + "width": 80, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-date-text", + "type": "text", + "x": 812, + "y": 108, + "width": 40, + "height": 16, + "text": "Date", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "view-options", + "type": "rectangle", + "x": 1316, + "y": 98, + "width": 88, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "view-options-text", + "type": "text", + "x": 1326, + "y": 108, + "width": 60, + "height": 16, + "text": "Columns", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-header-row", + "type": "rectangle", + "x": 304, + "y": 144, + "width": 1112, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-checkbox", + "type": "rectangle", + "x": 320, + "y": 158, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-user-col", + "type": "text", + "x": 360, + "y": 160, + "width": 40, + "height": 14, + "text": "User", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-role-col", + "type": "text", + "x": 580, + "y": 160, + "width": 40, + "height": 14, + "text": "Role", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-2fa-col", + "type": "text", + "x": 700, + "y": 160, + "width": 30, + "height": 14, + "text": "2FA", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-status-col", + "type": "text", + "x": 820, + "y": 160, + "width": 50, + "height": 14, + "text": "Status", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-created-col", + "type": "text", + "x": 980, + "y": 160, + "width": 60, + "height": 14, + "text": "Created", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-actions-col", + "type": "text", + "x": 1340, + "y": 160, + "width": 55, + "height": 14, + "text": "Actions", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-1", + "type": "rectangle", + "x": 304, + "y": 192, + "width": 1112, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-checkbox", + "type": "rectangle", + "x": 320, + "y": 210, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-avatar", + "type": "rectangle", + "x": 360, + "y": 206, + "width": 28, + "height": 28, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 14 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-name", + "type": "text", + "x": 396, + "y": 204, + "width": 80, + "height": 14, + "text": "John Doe", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-email", + "type": "text", + "x": 396, + "y": 220, + "width": 130, + "height": 12, + "text": "john@example.com", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-badge", + "type": "rectangle", + "x": 580, + "y": 210, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-role-text", + "type": "text", + "x": 592, + "y": 213, + "width": 40, + "height": 14, + "text": "Admin", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-2fa-text", + "type": "text", + "x": 705, + "y": 213, + "width": 20, + "height": 14, + "text": "Y", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#22c55e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status-badge", + "type": "rectangle", + "x": 820, + "y": 210, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status-text", + "type": "text", + "x": 832, + "y": 213, + "width": 40, + "height": 14, + "text": "Active", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#22c55e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-created", + "type": "text", + "x": 980, + "y": 213, + "width": 50, + "height": 14, + "text": "Jan 15", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-actions", + "type": "rectangle", + "x": 1356, + "y": 210, + "width": 24, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-2", + "type": "rectangle", + "x": 304, + "y": 248, + "width": 1112, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-checkbox", + "type": "rectangle", + "x": 320, + "y": 266, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-avatar", + "type": "rectangle", + "x": 360, + "y": 262, + "width": 28, + "height": 28, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 14 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-name", + "type": "text", + "x": 396, + "y": 260, + "width": 90, + "height": 14, + "text": "Jane Smith", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-email", + "type": "text", + "x": 396, + "y": 276, + "width": 130, + "height": 12, + "text": "jane@example.com", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-badge", + "type": "rectangle", + "x": 580, + "y": 266, + "width": 50, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-role-text", + "type": "text", + "x": 592, + "y": 269, + "width": 30, + "height": 14, + "text": "User", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-2fa-text", + "type": "text", + "x": 705, + "y": 269, + "width": 20, + "height": 14, + "text": "-", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status-badge", + "type": "rectangle", + "x": 820, + "y": 266, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status-text", + "type": "text", + "x": 832, + "y": 269, + "width": 40, + "height": 14, + "text": "Active", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#22c55e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-created", + "type": "text", + "x": 980, + "y": 269, + "width": 50, + "height": 14, + "text": "Jan 12", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-actions", + "type": "rectangle", + "x": 1356, + "y": 266, + "width": 24, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "data-row-3", + "type": "rectangle", + "x": 304, + "y": 304, + "width": 1112, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-checkbox", + "type": "rectangle", + "x": 320, + "y": 322, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-avatar", + "type": "rectangle", + "x": 360, + "y": 318, + "width": 28, + "height": 28, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 14 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-name", + "type": "text", + "x": 396, + "y": 316, + "width": 90, + "height": 14, + "text": "Bob Wilson", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-email", + "type": "text", + "x": 396, + "y": 332, + "width": 130, + "height": 12, + "text": "bob@example.com", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-badge", + "type": "rectangle", + "x": 580, + "y": 322, + "width": 50, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-role-text", + "type": "text", + "x": 592, + "y": 325, + "width": 30, + "height": 14, + "text": "User", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-2fa-text", + "type": "text", + "x": 705, + "y": 325, + "width": 20, + "height": 14, + "text": "Y", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#22c55e", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-status-badge", + "type": "rectangle", + "x": 820, + "y": 322, + "width": 60, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 10 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-status-text", + "type": "text", + "x": 828, + "y": 325, + "width": 45, + "height": 14, + "text": "Banned", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-created", + "type": "text", + "x": 980, + "y": 325, + "width": 50, + "height": 14, + "text": "Jan 10", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-actions", + "type": "rectangle", + "x": 1356, + "y": 322, + "width": 24, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "empty-rows-area", + "type": "rectangle", + "x": 304, + "y": 360, + "width": 1112, + "height": 460, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-bar", + "type": "rectangle", + "x": 304, + "y": 820, + "width": 1112, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-info", + "type": "text", + "x": 328, + "y": 836, + "width": 150, + "height": 16, + "text": "Showing 1-10 of 1,234", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 70, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-prev", + "type": "rectangle", + "x": 1124, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-1", + "type": "rectangle", + "x": 1164, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-1-text", + "type": "text", + "x": 1176, + "y": 838, + "width": 10, + "height": 12, + "text": "1", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-2", + "type": "rectangle", + "x": 1204, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-2-text", + "type": "text", + "x": 1216, + "y": 838, + "width": 10, + "height": 12, + "text": "2", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-3", + "type": "rectangle", + "x": 1244, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-3-text", + "type": "text", + "x": 1256, + "y": 838, + "width": 10, + "height": 12, + "text": "3", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-ellipsis", + "type": "text", + "x": 1284, + "y": 838, + "width": 20, + "height": 12, + "text": "...", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-10", + "type": "rectangle", + "x": 1316, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-10-text", + "type": "text", + "x": 1324, + "y": 838, + "width": 16, + "height": 12, + "text": "124", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-next", + "type": "rectangle", + "x": 1356, + "y": 828, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/medium/settings-billing.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/medium/settings-billing.excalidraw new file mode 100644 index 0000000..23a79cb --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/medium/settings-billing.excalidraw @@ -0,0 +1,2747 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-text", + "type": "text", + "x": 40, + "y": 24, + "width": 80, + "height": 16, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-1", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-1-text", + "type": "text", + "x": 40, + "y": 112, + "width": 80, + "height": 16, + "text": "Dashboard", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-2", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-2-text", + "type": "text", + "x": 40, + "y": 172, + "width": 80, + "height": 16, + "text": "Browse MCPs", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-3", + "type": "rectangle", + "x": 20, + "y": 220, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-3-text", + "type": "text", + "x": 40, + "y": 232, + "width": 80, + "height": 16, + "text": "Bundles", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-active", + "type": "rectangle", + "x": 20, + "y": 280, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-text", + "type": "text", + "x": 40, + "y": 292, + "width": 80, + "height": 16, + "text": "Settings", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user-text", + "type": "text", + "x": 40, + "y": 860, + "width": 100, + "height": 16, + "text": "John Doe", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "rectangle", + "x": 304, + "y": 20, + "width": 120, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title-text", + "type": "text", + "x": 314, + "y": 24, + "width": 100, + "height": 18, + "text": "Settings", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-general", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-general-text", + "type": "text", + "x": 320, + "y": 96, + "width": 48, + "height": 16, + "text": "General", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-security", + "type": "rectangle", + "x": 404, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-security-text", + "type": "text", + "x": 416, + "y": 96, + "width": 56, + "height": 16, + "text": "Security", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-billing-active", + "type": "rectangle", + "x": 504, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-billing-text", + "type": "text", + "x": 524, + "y": 96, + "width": 40, + "height": 16, + "text": "Billing", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-card", + "type": "rectangle", + "x": 304, + "y": 140, + "width": 540, + "height": 200, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "plan-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-title", + "type": "rectangle", + "x": 324, + "y": 160, + "width": 120, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "plan-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-title-text", + "type": "text", + "x": 324, + "y": 160, + "width": 120, + "height": 20, + "text": "Current Plan", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "plan-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-name-badge", + "type": "rectangle", + "x": 324, + "y": 200, + "width": 60, + "height": 28, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "plan-section" + ], + "roundness": { + "type": 3, + "value": 14 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-name-badge-text", + "type": "text", + "x": 340, + "y": 206, + "width": 28, + "height": 16, + "text": "Pro", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "plan-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-price", + "type": "rectangle", + "x": 400, + "y": 200, + "width": 100, + "height": 28, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "plan-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-price-text", + "type": "text", + "x": 400, + "y": 206, + "width": 100, + "height": 16, + "text": "$29/month", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "plan-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-feature-1", + "type": "rectangle", + "x": 324, + "y": 244, + "width": 200, + "height": 14, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "plan-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-feature-1-text", + "type": "text", + "x": 324, + "y": 244, + "width": 200, + "height": 14, + "text": "Unlimited chats", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "plan-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-feature-2", + "type": "rectangle", + "x": 324, + "y": 264, + "width": 180, + "height": 14, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "plan-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-feature-2-text", + "type": "text", + "x": 324, + "y": 264, + "width": 180, + "height": 14, + "text": "Priority support", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "plan-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-feature-3", + "type": "rectangle", + "x": 324, + "y": 284, + "width": 160, + "height": 14, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "plan-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "plan-feature-3-text", + "type": "text", + "x": 324, + "y": 284, + "width": 160, + "height": 14, + "text": "API access", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "plan-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "manage-subscription-button", + "type": "rectangle", + "x": 680, + "y": 180, + "width": 140, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "plan-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "manage-subscription-button-text", + "type": "text", + "x": 684, + "y": 192, + "width": 132, + "height": 16, + "text": "Manage subscription", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "plan-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-card", + "type": "rectangle", + "x": 864, + "y": 140, + "width": 280, + "height": 200, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "credits-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-title", + "type": "rectangle", + "x": 884, + "y": 160, + "width": 80, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "credits-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-title-text", + "type": "text", + "x": 884, + "y": 160, + "width": 80, + "height": 20, + "text": "Credits", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "credits-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-balance", + "type": "rectangle", + "x": 884, + "y": 200, + "width": 140, + "height": 32, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "credits-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-balance-text", + "type": "text", + "x": 884, + "y": 204, + "width": 140, + "height": 24, + "text": "2,450 remaining", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "credits-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-usage-bar-bg", + "type": "rectangle", + "x": 884, + "y": 252, + "width": 240, + "height": 12, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 40, + "groupIds": [ + "credits-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "credits-usage-bar-fill", + "type": "rectangle", + "x": 884, + "y": 252, + "width": 144, + "height": 12, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "credits-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "buy-credits-button", + "type": "rectangle", + "x": 884, + "y": 284, + "width": 120, + "height": 40, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "credits-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "buy-credits-button-text", + "type": "text", + "x": 904, + "y": 296, + "width": 80, + "height": 16, + "text": "Buy credits", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "credits-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "payment-method-card", + "type": "rectangle", + "x": 304, + "y": 360, + "width": 540, + "height": 100, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "payment-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "payment-title", + "type": "rectangle", + "x": 324, + "y": 380, + "width": 140, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "payment-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "payment-title-text", + "type": "text", + "x": 324, + "y": 380, + "width": 140, + "height": 20, + "text": "Payment Method", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "payment-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-icon", + "type": "rectangle", + "x": 324, + "y": 416, + "width": 40, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "payment-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-last-digits", + "type": "rectangle", + "x": 380, + "y": 420, + "width": 100, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "payment-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-last-digits-text", + "type": "text", + "x": 380, + "y": 420, + "width": 100, + "height": 16, + "text": "•••• 4242", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "payment-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "update-payment-button", + "type": "rectangle", + "x": 720, + "y": 400, + "width": 100, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "payment-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "update-payment-button-text", + "type": "text", + "x": 744, + "y": 412, + "width": 52, + "height": 16, + "text": "Update", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "payment-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "billing-history-card", + "type": "rectangle", + "x": 304, + "y": 480, + "width": 840, + "height": 340, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "history-title", + "type": "rectangle", + "x": 324, + "y": 500, + "width": 140, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "history-title-text", + "type": "text", + "x": 324, + "y": 500, + "width": 140, + "height": 20, + "text": "Billing History", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "table-header", + "type": "rectangle", + "x": 324, + "y": 540, + "width": 800, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 40, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-date", + "type": "rectangle", + "x": 344, + "y": 552, + "width": 60, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-date-text", + "type": "text", + "x": 344, + "y": 552, + "width": 60, + "height": 16, + "text": "Date", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-amount", + "type": "rectangle", + "x": 544, + "y": 552, + "width": 80, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-amount-text", + "type": "text", + "x": 544, + "y": 552, + "width": 80, + "height": 16, + "text": "Amount", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-status", + "type": "rectangle", + "x": 744, + "y": 552, + "width": 60, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-status-text", + "type": "text", + "x": 744, + "y": 552, + "width": 60, + "height": 16, + "text": "Status", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invoice", + "type": "rectangle", + "x": 944, + "y": 552, + "width": 80, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-invoice-text", + "type": "text", + "x": 944, + "y": 552, + "width": 80, + "height": 16, + "text": "Invoice", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1", + "type": "rectangle", + "x": 324, + "y": 580, + "width": 800, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-date", + "type": "rectangle", + "x": 344, + "y": 596, + "width": 100, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-date-text", + "type": "text", + "x": 344, + "y": 596, + "width": 100, + "height": 16, + "text": "Jan 1, 2024", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-amount", + "type": "rectangle", + "x": 544, + "y": 596, + "width": 60, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-amount-text", + "type": "text", + "x": 544, + "y": 596, + "width": 60, + "height": 16, + "text": "$29.00", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status", + "type": "rectangle", + "x": 744, + "y": 592, + "width": 60, + "height": 24, + "strokeColor": "#22c55e", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status-text", + "type": "text", + "x": 758, + "y": 596, + "width": 32, + "height": 16, + "text": "Paid", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-invoice", + "type": "rectangle", + "x": 944, + "y": 596, + "width": 80, + "height": 16, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-invoice-text", + "type": "text", + "x": 948, + "y": 596, + "width": 72, + "height": 16, + "text": "Download", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2", + "type": "rectangle", + "x": 324, + "y": 628, + "width": 800, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-date", + "type": "rectangle", + "x": 344, + "y": 644, + "width": 100, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-date-text", + "type": "text", + "x": 344, + "y": 644, + "width": 100, + "height": 16, + "text": "Dec 1, 2023", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-amount", + "type": "rectangle", + "x": 544, + "y": 644, + "width": 60, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-amount-text", + "type": "text", + "x": 544, + "y": 644, + "width": 60, + "height": 16, + "text": "$29.00", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status", + "type": "rectangle", + "x": 744, + "y": 640, + "width": 60, + "height": 24, + "strokeColor": "#22c55e", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status-text", + "type": "text", + "x": 758, + "y": 644, + "width": 32, + "height": 16, + "text": "Paid", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-invoice", + "type": "rectangle", + "x": 944, + "y": 644, + "width": 80, + "height": 16, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-invoice-text", + "type": "text", + "x": 948, + "y": 644, + "width": 72, + "height": 16, + "text": "Download", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3", + "type": "rectangle", + "x": 324, + "y": 676, + "width": 800, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-date", + "type": "rectangle", + "x": 344, + "y": 692, + "width": 100, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-date-text", + "type": "text", + "x": 344, + "y": 692, + "width": 100, + "height": 16, + "text": "Nov 1, 2023", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-amount", + "type": "rectangle", + "x": 544, + "y": 692, + "width": 60, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-amount-text", + "type": "text", + "x": 544, + "y": 692, + "width": 60, + "height": 16, + "text": "$29.00", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-status", + "type": "rectangle", + "x": 744, + "y": 688, + "width": 60, + "height": 24, + "strokeColor": "#22c55e", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-status-text", + "type": "text", + "x": 758, + "y": 692, + "width": 32, + "height": 16, + "text": "Paid", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-invoice", + "type": "rectangle", + "x": 944, + "y": 692, + "width": 80, + "height": 16, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-invoice-text", + "type": "text", + "x": 948, + "y": 692, + "width": 72, + "height": 16, + "text": "Download", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4", + "type": "rectangle", + "x": 324, + "y": 724, + "width": 800, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-date", + "type": "rectangle", + "x": 344, + "y": 740, + "width": 100, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-date-text", + "type": "text", + "x": 344, + "y": 740, + "width": 100, + "height": 16, + "text": "Oct 1, 2023", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-amount", + "type": "rectangle", + "x": 544, + "y": 740, + "width": 60, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-amount-text", + "type": "text", + "x": 544, + "y": 740, + "width": 60, + "height": 16, + "text": "$29.00", + "fontSize": 13, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-status", + "type": "rectangle", + "x": 744, + "y": 736, + "width": 60, + "height": 24, + "strokeColor": "#22c55e", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-status-text", + "type": "text", + "x": 758, + "y": 740, + "width": 32, + "height": 16, + "text": "Paid", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-invoice", + "type": "rectangle", + "x": 944, + "y": 740, + "width": 80, + "height": 16, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "history-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-invoice-text", + "type": "text", + "x": 948, + "y": 740, + "width": 72, + "height": 16, + "text": "Download", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "history-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/medium/settings-general.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/medium/settings-general.excalidraw new file mode 100644 index 0000000..6136026 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/medium/settings-general.excalidraw @@ -0,0 +1,1556 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-text", + "type": "text", + "x": 40, + "y": 24, + "width": 80, + "height": 16, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-1", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-1-text", + "type": "text", + "x": 40, + "y": 112, + "width": 80, + "height": 16, + "text": "Dashboard", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-2", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-2-text", + "type": "text", + "x": 40, + "y": 172, + "width": 80, + "height": 16, + "text": "Browse MCPs", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-3", + "type": "rectangle", + "x": 20, + "y": 220, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-3-text", + "type": "text", + "x": 40, + "y": 232, + "width": 80, + "height": 16, + "text": "Bundles", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-active", + "type": "rectangle", + "x": 20, + "y": 280, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-text", + "type": "text", + "x": 40, + "y": 292, + "width": 80, + "height": 16, + "text": "Settings", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user-text", + "type": "text", + "x": 40, + "y": 860, + "width": 100, + "height": 16, + "text": "John Doe", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "rectangle", + "x": 304, + "y": 20, + "width": 120, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title-text", + "type": "text", + "x": 314, + "y": 24, + "width": 100, + "height": 18, + "text": "Settings", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-general-active", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-general-text", + "type": "text", + "x": 320, + "y": 96, + "width": 48, + "height": 16, + "text": "General", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-security", + "type": "rectangle", + "x": 404, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-security-text", + "type": "text", + "x": 416, + "y": 96, + "width": 56, + "height": 16, + "text": "Security", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-billing", + "type": "rectangle", + "x": 504, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-billing-text", + "type": "text", + "x": 524, + "y": 96, + "width": 40, + "height": 16, + "text": "Billing", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "profile-card", + "type": "rectangle", + "x": 304, + "y": 140, + "width": 700, + "height": 280, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "profile-card-title", + "type": "rectangle", + "x": 324, + "y": 160, + "width": 100, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "profile-card-title-text", + "type": "text", + "x": 324, + "y": 160, + "width": 100, + "height": 20, + "text": "Profile", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "avatar-circle", + "type": "ellipse", + "x": 324, + "y": 200, + "width": 80, + "height": 80, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "avatar-initials", + "type": "text", + "x": 344, + "y": 232, + "width": 40, + "height": 16, + "text": "JD", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "profile-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "avatar-change-button", + "type": "rectangle", + "x": 324, + "y": 300, + "width": 100, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "avatar-change-button-text", + "type": "text", + "x": 330, + "y": 308, + "width": 88, + "height": 16, + "text": "Change avatar", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-label", + "type": "rectangle", + "x": 440, + "y": 200, + "width": 60, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "profile-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-label-text", + "type": "text", + "x": 440, + "y": 200, + "width": 60, + "height": 16, + "text": "Full name", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-input", + "type": "rectangle", + "x": 440, + "y": 220, + "width": 540, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "name-input-text", + "type": "text", + "x": 456, + "y": 232, + "width": 80, + "height": 16, + "text": "John Doe", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-label", + "type": "rectangle", + "x": 440, + "y": 280, + "width": 60, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "profile-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-label-text", + "type": "text", + "x": 440, + "y": 280, + "width": 60, + "height": 16, + "text": "Email", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-input", + "type": "rectangle", + "x": 440, + "y": 300, + "width": 440, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "email-input-text", + "type": "text", + "x": 456, + "y": 312, + "width": 140, + "height": 16, + "text": "john@example.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "verified-badge", + "type": "rectangle", + "x": 900, + "y": 308, + "width": 80, + "height": 24, + "strokeColor": "#22c55e", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "verified-badge-text", + "type": "text", + "x": 916, + "y": 312, + "width": 48, + "height": 16, + "text": "Verified", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "save-profile-button", + "type": "rectangle", + "x": 324, + "y": 360, + "width": 120, + "height": 40, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "save-profile-button-text", + "type": "text", + "x": 344, + "y": 372, + "width": 80, + "height": 16, + "text": "Save changes", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "profile-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-card", + "type": "rectangle", + "x": 304, + "y": 440, + "width": 700, + "height": 140, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "language-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-card-title", + "type": "rectangle", + "x": 324, + "y": 460, + "width": 100, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "language-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-card-title-text", + "type": "text", + "x": 324, + "y": 460, + "width": 100, + "height": 20, + "text": "Language", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "language-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-label", + "type": "rectangle", + "x": 324, + "y": 500, + "width": 120, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "language-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-label-text", + "type": "text", + "x": 324, + "y": 500, + "width": 120, + "height": 16, + "text": "Display language", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "language-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-dropdown", + "type": "rectangle", + "x": 324, + "y": 520, + "width": 300, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "language-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "language-dropdown-text", + "type": "text", + "x": 340, + "y": 532, + "width": 60, + "height": 16, + "text": "English", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "language-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "dropdown-chevron", + "type": "rectangle", + "x": 588, + "y": 532, + "width": 16, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "language-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "danger-zone-card", + "type": "rectangle", + "x": 304, + "y": 600, + "width": 700, + "height": 160, + "strokeColor": "#ef4444", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "danger-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "danger-zone-title", + "type": "rectangle", + "x": 324, + "y": 620, + "width": 120, + "height": 20, + "strokeColor": "#ef4444", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "danger-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "danger-zone-title-text", + "type": "text", + "x": 324, + "y": 620, + "width": 120, + "height": 20, + "text": "Danger Zone", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "danger-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "danger-zone-description", + "type": "rectangle", + "x": 324, + "y": 660, + "width": 400, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "danger-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "danger-zone-description-text", + "type": "text", + "x": 324, + "y": 660, + "width": 400, + "height": 16, + "text": "Delete your account permanently. This action cannot be undone.", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "danger-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "delete-account-button", + "type": "rectangle", + "x": 324, + "y": 700, + "width": 140, + "height": 40, + "strokeColor": "#ef4444", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "danger-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "delete-account-button-text", + "type": "text", + "x": 340, + "y": 712, + "width": 108, + "height": 16, + "text": "Delete Account", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "danger-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/medium/settings-security.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/medium/settings-security.excalidraw new file mode 100644 index 0000000..1d8b2db --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/medium/settings-security.excalidraw @@ -0,0 +1,1892 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-text", + "type": "text", + "x": 40, + "y": 24, + "width": 80, + "height": 16, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-1", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-1-text", + "type": "text", + "x": 40, + "y": 112, + "width": 80, + "height": 16, + "text": "Dashboard", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-2", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-2-text", + "type": "text", + "x": 40, + "y": 172, + "width": 80, + "height": 16, + "text": "Browse MCPs", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-3", + "type": "rectangle", + "x": 20, + "y": 220, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-3-text", + "type": "text", + "x": 40, + "y": 232, + "width": 80, + "height": 16, + "text": "Bundles", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-active", + "type": "rectangle", + "x": 20, + "y": 280, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-settings-text", + "type": "text", + "x": 40, + "y": 292, + "width": 80, + "height": 16, + "text": "Settings", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user-text", + "type": "text", + "x": 40, + "y": 860, + "width": 100, + "height": 16, + "text": "John Doe", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "rectangle", + "x": 304, + "y": 20, + "width": 120, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title-text", + "type": "text", + "x": 314, + "y": 24, + "width": 100, + "height": 18, + "text": "Settings", + "fontSize": 18, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-general", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-general-text", + "type": "text", + "x": 320, + "y": 96, + "width": 48, + "height": 16, + "text": "General", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-security-active", + "type": "rectangle", + "x": 404, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-security-text", + "type": "text", + "x": 416, + "y": 96, + "width": 56, + "height": 16, + "text": "Security", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-billing", + "type": "rectangle", + "x": 504, + "y": 88, + "width": 80, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "tab-billing-text", + "type": "text", + "x": 524, + "y": 96, + "width": 40, + "height": 16, + "text": "Billing", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "tabs-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-card", + "type": "rectangle", + "x": 304, + "y": 140, + "width": 700, + "height": 140, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "twofa-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-title", + "type": "rectangle", + "x": 324, + "y": 160, + "width": 200, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "twofa-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-title-text", + "type": "text", + "x": 324, + "y": 160, + "width": 200, + "height": 20, + "text": "Two-Factor Authentication", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "twofa-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-status-indicator", + "type": "rectangle", + "x": 324, + "y": 200, + "width": 80, + "height": 24, + "strokeColor": "#22c55e", + "backgroundColor": "#22c55e", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "twofa-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-status-text", + "type": "text", + "x": 340, + "y": 204, + "width": 48, + "height": 16, + "text": "Enabled", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "twofa-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-description", + "type": "rectangle", + "x": 324, + "y": 240, + "width": 500, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "twofa-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-description-text", + "type": "text", + "x": 324, + "y": 240, + "width": 500, + "height": 16, + "text": "Add an extra layer of security to your account with two-factor authentication.", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "twofa-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-toggle-button", + "type": "rectangle", + "x": 880, + "y": 180, + "width": 100, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "twofa-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "twofa-toggle-button-text", + "type": "text", + "x": 904, + "y": 192, + "width": 52, + "height": 16, + "text": "Disable", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "twofa-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkeys-card", + "type": "rectangle", + "x": 304, + "y": 300, + "width": 700, + "height": 220, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "passkeys-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkeys-title", + "type": "rectangle", + "x": 324, + "y": 320, + "width": 100, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "passkeys-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkeys-title-text", + "type": "text", + "x": 324, + "y": 320, + "width": 100, + "height": 20, + "text": "Passkeys", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "passkeys-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-item-1", + "type": "rectangle", + "x": 324, + "y": 360, + "width": 656, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "passkeys-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-1-name", + "type": "rectangle", + "x": 340, + "y": 372, + "width": 120, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "passkeys-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-1-name-text", + "type": "text", + "x": 340, + "y": 372, + "width": 120, + "height": 16, + "text": "MacBook Pro", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "passkeys-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-1-date", + "type": "rectangle", + "x": 500, + "y": 372, + "width": 120, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "passkeys-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-1-date-text", + "type": "text", + "x": 500, + "y": 372, + "width": 120, + "height": 16, + "text": "Created Jan 15", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "passkeys-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-1-delete", + "type": "rectangle", + "x": 920, + "y": 368, + "width": 48, + "height": 24, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "passkeys-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-1-delete-text", + "type": "text", + "x": 926, + "y": 372, + "width": 36, + "height": 16, + "text": "Delete", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "passkeys-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-item-2", + "type": "rectangle", + "x": 324, + "y": 420, + "width": 656, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "passkeys-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-2-name", + "type": "rectangle", + "x": 340, + "y": 432, + "width": 140, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "passkeys-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-2-name-text", + "type": "text", + "x": 340, + "y": 432, + "width": 140, + "height": 16, + "text": "iPhone", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "passkeys-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-2-date", + "type": "rectangle", + "x": 520, + "y": 432, + "width": 120, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "passkeys-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-2-date-text", + "type": "text", + "x": 520, + "y": 432, + "width": 120, + "height": 16, + "text": "Created Jan 20", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "passkeys-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-2-delete", + "type": "rectangle", + "x": 920, + "y": 428, + "width": 48, + "height": 24, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "passkeys-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "passkey-2-delete-text", + "type": "text", + "x": 926, + "y": 432, + "width": 36, + "height": 16, + "text": "Delete", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "passkeys-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "add-passkey-button", + "type": "rectangle", + "x": 324, + "y": 480, + "width": 120, + "height": 40, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "passkeys-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "add-passkey-button-text", + "type": "text", + "x": 340, + "y": 492, + "width": 88, + "height": 16, + "text": "Add passkey", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "passkeys-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sessions-card", + "type": "rectangle", + "x": 304, + "y": 540, + "width": 700, + "height": 220, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sessions-title", + "type": "rectangle", + "x": 324, + "y": 560, + "width": 140, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sessions-title-text", + "type": "text", + "x": 324, + "y": 560, + "width": 140, + "height": 20, + "text": "Active Sessions", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sessions-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-item-1", + "type": "rectangle", + "x": 324, + "y": 600, + "width": 656, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-icon", + "type": "rectangle", + "x": 340, + "y": 612, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-name", + "type": "rectangle", + "x": 380, + "y": 608, + "width": 160, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-name-text", + "type": "text", + "x": 380, + "y": 608, + "width": 160, + "height": 16, + "text": "Chrome on macOS", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sessions-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-location", + "type": "rectangle", + "x": 380, + "y": 628, + "width": 120, + "height": 12, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-location-text", + "type": "text", + "x": 380, + "y": 628, + "width": 120, + "height": 12, + "text": "San Francisco, CA", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sessions-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-current-badge", + "type": "rectangle", + "x": 900, + "y": 612, + "width": 68, + "height": 24, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-1-current-badge-text", + "type": "text", + "x": 912, + "y": 616, + "width": 44, + "height": 16, + "text": "Current", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sessions-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-item-2", + "type": "rectangle", + "x": 324, + "y": 660, + "width": 656, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-icon", + "type": "rectangle", + "x": 340, + "y": 672, + "width": 24, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-name", + "type": "rectangle", + "x": 380, + "y": 668, + "width": 140, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-name-text", + "type": "text", + "x": 380, + "y": 668, + "width": 140, + "height": 16, + "text": "Safari on iPhone", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sessions-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-location", + "type": "rectangle", + "x": 380, + "y": 688, + "width": 100, + "height": 12, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-location-text", + "type": "text", + "x": 380, + "y": 688, + "width": 100, + "height": 12, + "text": "San Francisco", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "sessions-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-revoke", + "type": "rectangle", + "x": 908, + "y": 672, + "width": 60, + "height": 24, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sessions-section" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "session-2-revoke-text", + "type": "text", + "x": 916, + "y": 676, + "width": 44, + "height": 16, + "text": "Revoke", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sessions-section" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/medium/sidebar-admin.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/medium/sidebar-admin.excalidraw new file mode 100644 index 0000000..c3bfe33 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/medium/sidebar-admin.excalidraw @@ -0,0 +1,887 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "sidebar-container", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-area", + "type": "rectangle", + "x": 24, + "y": 24, + "width": 160, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-icon", + "type": "rectangle", + "x": 36, + "y": 32, + "width": 24, + "height": 24, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 68, + "y": 34, + "width": 80, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 14 + }, + { + "id": "admin-badge", + "type": "rectangle", + "x": 196, + "y": 32, + "width": 60, + "height": 24, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "admin-badge-text", + "type": "text", + "x": 206, + "y": 36, + "width": 40, + "height": 16, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Admin", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 10 + }, + { + "id": "section-divider-1", + "type": "line", + "x": 24, + "y": 88, + "width": 232, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 232, + 0 + ] + ] + }, + { + "id": "section-label-admin-text", + "type": "text", + "x": 24, + "y": 100, + "width": 50, + "height": 14, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "section-admin" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "ADMIN", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "nav-home-active", + "type": "rectangle", + "x": 24, + "y": 120, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-home-icon", + "type": "rectangle", + "x": 36, + "y": 130, + "width": 20, + "height": 20, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-home-text", + "type": "text", + "x": 64, + "y": 130, + "width": 60, + "height": 20, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Home", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-users", + "type": "rectangle", + "x": 24, + "y": 168, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-users-icon", + "type": "rectangle", + "x": 36, + "y": 178, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-users-text", + "type": "text", + "x": 64, + "y": 178, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Users", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-orgs", + "type": "rectangle", + "x": 24, + "y": 216, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-orgs-icon", + "type": "rectangle", + "x": 36, + "y": 226, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-orgs-text", + "type": "text", + "x": 64, + "y": 226, + "width": 100, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Organizations", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-customers", + "type": "rectangle", + "x": 24, + "y": 264, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-customers-icon", + "type": "rectangle", + "x": 36, + "y": 274, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-customers-text", + "type": "text", + "x": 64, + "y": 274, + "width": 80, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-admin" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Customers", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-2", + "type": "line", + "x": 24, + "y": 324, + "width": 232, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 232, + 0 + ] + ] + }, + { + "id": "nav-back", + "type": "rectangle", + "x": 24, + "y": 344, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-back" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-back-arrow", + "type": "text", + "x": 36, + "y": 354, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-back" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "<-", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-back-text", + "type": "text", + "x": 64, + "y": 354, + "width": 140, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-back" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Back to Dashboard", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-footer", + "type": "line", + "x": 24, + "y": 812, + "width": 232, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 232, + 0 + ] + ] + }, + { + "id": "user-footer", + "type": "rectangle", + "x": 24, + "y": 824, + "width": 232, + "height": 64, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-avatar", + "type": "ellipse", + "x": 24, + "y": 836, + "width": 40, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-avatar-initials", + "type": "text", + "x": 32, + "y": 848, + "width": 24, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "AU", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 10 + }, + { + "id": "user-name", + "type": "text", + "x": 76, + "y": 838, + "width": 100, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Admin User", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 12 + }, + { + "id": "role-badge", + "type": "rectangle", + "x": 76, + "y": 858, + "width": 50, + "height": 18, + "strokeColor": "#ef4444", + "backgroundColor": "#ef4444", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "role-badge-text", + "type": "text", + "x": 84, + "y": 860, + "width": 34, + "height": 14, + "strokeColor": "#ef4444", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "admin", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "dropdown-indicator", + "type": "rectangle", + "x": 224, + "y": 848, + "width": 16, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/medium/sidebar-apps.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/medium/sidebar-apps.excalidraw new file mode 100644 index 0000000..a0b2e5d --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/medium/sidebar-apps.excalidraw @@ -0,0 +1,1460 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "sidebar-container", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-area", + "type": "rectangle", + "x": 24, + "y": 24, + "width": 232, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-icon", + "type": "rectangle", + "x": 36, + "y": 32, + "width": 24, + "height": 24, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 68, + "y": 34, + "width": 80, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 14 + }, + { + "id": "section-divider-1", + "type": "line", + "x": 24, + "y": 88, + "width": 232, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 232, + 0 + ] + ] + }, + { + "id": "section-label-apps-text", + "type": "text", + "x": 24, + "y": 100, + "width": 40, + "height": 14, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "section-apps" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "APPS", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "nav-chat-active", + "type": "rectangle", + "x": 24, + "y": 120, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-chat-icon", + "type": "rectangle", + "x": 36, + "y": 130, + "width": 20, + "height": 20, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-chat-text", + "type": "text", + "x": 64, + "y": 130, + "width": 60, + "height": 20, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Chat", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-image", + "type": "rectangle", + "x": 24, + "y": 168, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-image-icon", + "type": "rectangle", + "x": 36, + "y": 178, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-image-text", + "type": "text", + "x": 64, + "y": 178, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Image", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-tts", + "type": "rectangle", + "x": 24, + "y": 216, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-tts-icon", + "type": "rectangle", + "x": 36, + "y": 226, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-tts-text", + "type": "text", + "x": 64, + "y": 226, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "TTS", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-pdf", + "type": "rectangle", + "x": 24, + "y": 264, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-pdf-icon", + "type": "rectangle", + "x": 36, + "y": 274, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-pdf-text", + "type": "text", + "x": 64, + "y": 274, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "PDF", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-agent", + "type": "rectangle", + "x": 24, + "y": 312, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-agent-icon", + "type": "rectangle", + "x": 36, + "y": 322, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-agent-text", + "type": "text", + "x": 64, + "y": 322, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-apps" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Agent", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-2", + "type": "line", + "x": 24, + "y": 372, + "width": 232, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 232, + 0 + ] + ] + }, + { + "id": "section-label-free-tools-text", + "type": "text", + "x": 24, + "y": 384, + "width": 80, + "height": 14, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "section-free-tools" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "FREE TOOLS", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "nav-envin", + "type": "rectangle", + "x": 24, + "y": 404, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-envin-icon", + "type": "rectangle", + "x": 36, + "y": 414, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-envin-text", + "type": "text", + "x": 64, + "y": 414, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Envin", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-ideas", + "type": "rectangle", + "x": 24, + "y": 452, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-ideas-icon", + "type": "rectangle", + "x": 36, + "y": 462, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-ideas-text", + "type": "text", + "x": 64, + "y": 462, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Ideas", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-extro", + "type": "rectangle", + "x": 24, + "y": 500, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-extro-icon", + "type": "rectangle", + "x": 36, + "y": 510, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-extro-text", + "type": "text", + "x": 64, + "y": 510, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Extro", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-emojai", + "type": "rectangle", + "x": 24, + "y": 548, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-emojai-icon", + "type": "rectangle", + "x": 36, + "y": 558, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-emojai-text", + "type": "text", + "x": 64, + "y": 558, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-free-tools" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Emojai", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-3", + "type": "line", + "x": 24, + "y": 608, + "width": 232, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 232, + 0 + ] + ] + }, + { + "id": "section-label-other-text", + "type": "text", + "x": 24, + "y": 620, + "width": 50, + "height": 14, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "section-other" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "OTHER", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "nav-home", + "type": "rectangle", + "x": 24, + "y": 640, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-other" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-home-icon", + "type": "rectangle", + "x": 36, + "y": 650, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-other" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-home-text", + "type": "text", + "x": 64, + "y": 650, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-other" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Home", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-docs", + "type": "rectangle", + "x": 24, + "y": 688, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-other" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-docs-icon", + "type": "rectangle", + "x": 36, + "y": 698, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-other" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-docs-text", + "type": "text", + "x": 64, + "y": 698, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-other" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Docs", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-blog", + "type": "rectangle", + "x": 24, + "y": 736, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-other" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-blog-icon", + "type": "rectangle", + "x": 36, + "y": 746, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-other" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-blog-text", + "type": "text", + "x": 64, + "y": 746, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-other" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Blog", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-footer", + "type": "line", + "x": 24, + "y": 812, + "width": 232, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 232, + 0 + ] + ] + }, + { + "id": "user-footer", + "type": "rectangle", + "x": 24, + "y": 824, + "width": 232, + "height": 64, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-avatar", + "type": "ellipse", + "x": 24, + "y": 836, + "width": 40, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-avatar-initials", + "type": "text", + "x": 32, + "y": 848, + "width": 24, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "JD", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 10 + }, + { + "id": "user-name", + "type": "text", + "x": 76, + "y": 838, + "width": 120, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "John Doe", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 12 + }, + { + "id": "user-email", + "type": "text", + "x": 76, + "y": 858, + "width": 140, + "height": 14, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "john@example.com", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 10 + }, + { + "id": "dropdown-indicator", + "type": "rectangle", + "x": 224, + "y": 848, + "width": 16, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/medium/sidebar-dashboard.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/medium/sidebar-dashboard.excalidraw new file mode 100644 index 0000000..a111f2a --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/medium/sidebar-dashboard.excalidraw @@ -0,0 +1,977 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "sidebar-container", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-area", + "type": "rectangle", + "x": 24, + "y": 24, + "width": 232, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-icon", + "type": "rectangle", + "x": 36, + "y": 32, + "width": 24, + "height": 24, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 68, + "y": 34, + "width": 80, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "MCPGet", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 14 + }, + { + "id": "section-divider-1", + "type": "line", + "x": 24, + "y": 88, + "width": 232, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 232, + 0 + ] + ] + }, + { + "id": "section-label-platform-text", + "type": "text", + "x": 24, + "y": 100, + "width": 70, + "height": 14, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "section-platform" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "PLATFORM", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "nav-dashboard-active", + "type": "rectangle", + "x": 24, + "y": 120, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-platform" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-dashboard-icon", + "type": "rectangle", + "x": 36, + "y": 130, + "width": 20, + "height": 20, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-platform" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-dashboard-text", + "type": "text", + "x": 64, + "y": 130, + "width": 80, + "height": 20, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-platform" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Dashboard", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-ai-tools", + "type": "rectangle", + "x": 24, + "y": 168, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-platform" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-ai-tools-icon", + "type": "rectangle", + "x": 36, + "y": 178, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-platform" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-ai-tools-text", + "type": "text", + "x": 64, + "y": 178, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-platform" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "AI Tools", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-2", + "type": "line", + "x": 24, + "y": 228, + "width": 232, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 232, + 0 + ] + ] + }, + { + "id": "section-label-manage-text", + "type": "text", + "x": 24, + "y": 240, + "width": 60, + "height": 14, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "section-manage" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "MANAGE", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "nav-settings", + "type": "rectangle", + "x": 24, + "y": 260, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-manage" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-settings-icon", + "type": "rectangle", + "x": 36, + "y": 270, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-manage" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-settings-text", + "type": "text", + "x": 64, + "y": 270, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-manage" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Settings", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-3", + "type": "line", + "x": 24, + "y": 320, + "width": 232, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 232, + 0 + ] + ] + }, + { + "id": "section-label-dev-text", + "type": "text", + "x": 24, + "y": 332, + "width": 30, + "height": 14, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "section-dev" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "DEV", + "fontSize": 11, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 9 + }, + { + "id": "nav-demos", + "type": "rectangle", + "x": 24, + "y": 352, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-dev" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-demos-icon", + "type": "rectangle", + "x": 36, + "y": 362, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-dev" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-demos-text", + "type": "text", + "x": 64, + "y": 362, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-dev" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Demos", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-bottom", + "type": "line", + "x": 24, + "y": 700, + "width": 232, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 232, + 0 + ] + ] + }, + { + "id": "nav-support", + "type": "rectangle", + "x": 24, + "y": 712, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-bottom" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-support-icon", + "type": "rectangle", + "x": 36, + "y": 722, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-bottom" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-support-text", + "type": "text", + "x": 64, + "y": 722, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-bottom" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Support", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-feedback", + "type": "rectangle", + "x": 24, + "y": 760, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-bottom" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-feedback-icon", + "type": "rectangle", + "x": 36, + "y": 770, + "width": 20, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-bottom" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-feedback-text", + "type": "text", + "x": 64, + "y": 770, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-bottom" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Feedback", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-footer", + "type": "line", + "x": 24, + "y": 812, + "width": 232, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 232, + 0 + ] + ] + }, + { + "id": "user-footer", + "type": "rectangle", + "x": 24, + "y": 824, + "width": 232, + "height": 64, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-avatar", + "type": "ellipse", + "x": 24, + "y": 836, + "width": 40, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-avatar-initials", + "type": "text", + "x": 32, + "y": 848, + "width": 24, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "JD", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 10 + }, + { + "id": "user-name", + "type": "text", + "x": 76, + "y": 846, + "width": 120, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "John Doe", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "dropdown-indicator", + "type": "rectangle", + "x": 224, + "y": 848, + "width": 16, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/templates/components/card-grid.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/templates/components/card-grid.excalidraw new file mode 100644 index 0000000..19b2117 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/templates/components/card-grid.excalidraw @@ -0,0 +1,755 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "card-1", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 352, + "height": 180, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "card-group-1" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-1-icon", + "type": "ellipse", + "x": 20, + "y": 20, + "width": 40, + "height": 40, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "card-group-1" + ], + "roundness": { + "type": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-1-title", + "type": "rectangle", + "x": 20, + "y": 72, + "width": 200, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "card-group-1" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-1-desc", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 300, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "card-group-1" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-1-action", + "type": "rectangle", + "x": 20, + "y": 144, + "width": 100, + "height": 24, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "card-group-1" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-2", + "type": "rectangle", + "x": 368, + "y": 0, + "width": 352, + "height": 180, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "card-group-2" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-2-icon", + "type": "ellipse", + "x": 388, + "y": 20, + "width": 40, + "height": 40, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "card-group-2" + ], + "roundness": { + "type": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-2-title", + "type": "rectangle", + "x": 388, + "y": 72, + "width": 200, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "card-group-2" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-2-desc", + "type": "rectangle", + "x": 388, + "y": 100, + "width": 300, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "card-group-2" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-2-action", + "type": "rectangle", + "x": 388, + "y": 144, + "width": 100, + "height": 24, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "card-group-2" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-3", + "type": "rectangle", + "x": 736, + "y": 0, + "width": 352, + "height": 180, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "card-group-3" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-3-icon", + "type": "ellipse", + "x": 756, + "y": 20, + "width": 40, + "height": 40, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "card-group-3" + ], + "roundness": { + "type": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-3-title", + "type": "rectangle", + "x": 756, + "y": 72, + "width": 200, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "card-group-3" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-3-desc", + "type": "rectangle", + "x": 756, + "y": 100, + "width": 300, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "card-group-3" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-3-action", + "type": "rectangle", + "x": 756, + "y": 144, + "width": 100, + "height": 24, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "card-group-3" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-4", + "type": "rectangle", + "x": 0, + "y": 196, + "width": 352, + "height": 180, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "card-group-4" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-4-icon", + "type": "ellipse", + "x": 20, + "y": 216, + "width": 40, + "height": 40, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "card-group-4" + ], + "roundness": { + "type": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-4-title", + "type": "rectangle", + "x": 20, + "y": 268, + "width": 200, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "card-group-4" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-4-desc", + "type": "rectangle", + "x": 20, + "y": 296, + "width": 300, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "card-group-4" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-4-action", + "type": "rectangle", + "x": 20, + "y": 340, + "width": 100, + "height": 24, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "card-group-4" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-5", + "type": "rectangle", + "x": 368, + "y": 196, + "width": 352, + "height": 180, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "card-group-5" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-5-icon", + "type": "ellipse", + "x": 388, + "y": 216, + "width": 40, + "height": 40, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "card-group-5" + ], + "roundness": { + "type": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-5-title", + "type": "rectangle", + "x": 388, + "y": 268, + "width": 200, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "card-group-5" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-5-desc", + "type": "rectangle", + "x": 388, + "y": 296, + "width": 300, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "card-group-5" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-5-action", + "type": "rectangle", + "x": 388, + "y": 340, + "width": 100, + "height": 24, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "card-group-5" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-6", + "type": "rectangle", + "x": 736, + "y": 196, + "width": 352, + "height": 180, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "card-group-6" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-6-icon", + "type": "ellipse", + "x": 756, + "y": 216, + "width": 40, + "height": 40, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "card-group-6" + ], + "roundness": { + "type": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-6-title", + "type": "rectangle", + "x": 756, + "y": 268, + "width": 200, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "card-group-6" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-6-desc", + "type": "rectangle", + "x": 756, + "y": 296, + "width": 300, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "card-group-6" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "card-6-action", + "type": "rectangle", + "x": 756, + "y": 340, + "width": 100, + "height": 24, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "card-group-6" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/templates/components/data-table.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/templates/components/data-table.excalidraw new file mode 100644 index 0000000..470ce68 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/templates/components/data-table.excalidraw @@ -0,0 +1,1867 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "table-container", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1112, + "height": 600, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "toolbar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1112, + "height": 56, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-input", + "type": "rectangle", + "x": 12, + "y": 10, + "width": 240, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "search-placeholder", + "type": "text", + "x": 24, + "y": 18, + "width": 80, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Search...", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "filter-button-1", + "type": "rectangle", + "x": 264, + "y": 10, + "width": 100, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-button-1-text", + "type": "text", + "x": 284, + "y": 18, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Status", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "filter-button-2", + "type": "rectangle", + "x": 376, + "y": 10, + "width": 100, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "filter-button-2-text", + "type": "text", + "x": 396, + "y": 18, + "width": 60, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Category", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "view-options", + "type": "rectangle", + "x": 980, + "y": 10, + "width": 120, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "view-options-text", + "type": "text", + "x": 1000, + "y": 18, + "width": 80, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "toolbar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "View", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "table-header-row", + "type": "rectangle", + "x": 0, + "y": 56, + "width": 1112, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-checkbox", + "type": "rectangle", + "x": 16, + "y": 70, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-name", + "type": "text", + "x": 60, + "y": 72, + "width": 60, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Name", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "header-email", + "type": "text", + "x": 260, + "y": 72, + "width": 60, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Email", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "header-role", + "type": "text", + "x": 520, + "y": 72, + "width": 40, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Role", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "header-status", + "type": "text", + "x": 680, + "y": 72, + "width": 50, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Status", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "header-actions", + "type": "text", + "x": 1020, + "y": 72, + "width": 60, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "header-row-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Actions", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "data-row-1", + "type": "rectangle", + "x": 0, + "y": 104, + "width": 1112, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-checkbox", + "type": "rectangle", + "x": 16, + "y": 118, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-name", + "type": "text", + "x": 60, + "y": 120, + "width": 100, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "John Smith", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "row-1-email", + "type": "text", + "x": 260, + "y": 120, + "width": 140, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "john@example.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "row-1-role", + "type": "text", + "x": 520, + "y": 120, + "width": 50, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Admin", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "row-1-status-badge", + "type": "rectangle", + "x": 680, + "y": 114, + "width": 60, + "height": 28, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": { + "type": 3, + "value": 14 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-1-status-text", + "type": "text", + "x": 692, + "y": 120, + "width": 36, + "height": 16, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-1-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Active", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 10 + }, + { + "id": "data-row-2", + "type": "rectangle", + "x": 0, + "y": 152, + "width": 1112, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-checkbox", + "type": "rectangle", + "x": 16, + "y": 166, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-name", + "type": "text", + "x": 60, + "y": 168, + "width": 100, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Jane Doe", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "row-2-email", + "type": "text", + "x": 260, + "y": 168, + "width": 140, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "jane@example.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "row-2-role", + "type": "text", + "x": 520, + "y": 168, + "width": 50, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Editor", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "row-2-status-badge", + "type": "rectangle", + "x": 680, + "y": 162, + "width": 60, + "height": 28, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": { + "type": 3, + "value": 14 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-2-status-text", + "type": "text", + "x": 692, + "y": 168, + "width": 36, + "height": 16, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-2-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Active", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 10 + }, + { + "id": "data-row-3", + "type": "rectangle", + "x": 0, + "y": 200, + "width": 1112, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-checkbox", + "type": "rectangle", + "x": 16, + "y": 214, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-name", + "type": "text", + "x": 60, + "y": 216, + "width": 100, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Bob Wilson", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "row-3-email", + "type": "text", + "x": 260, + "y": 216, + "width": 140, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "bob@example.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "row-3-role", + "type": "text", + "x": 520, + "y": 216, + "width": 50, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Viewer", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "row-3-status-badge", + "type": "rectangle", + "x": 680, + "y": 210, + "width": 60, + "height": 28, + "strokeColor": "transparent", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": { + "type": 3, + "value": 14 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-3-status-text", + "type": "text", + "x": 688, + "y": 216, + "width": 44, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-3-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Inactive", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 10 + }, + { + "id": "data-row-4", + "type": "rectangle", + "x": 0, + "y": 248, + "width": 1112, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-checkbox", + "type": "rectangle", + "x": 16, + "y": 262, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-name", + "type": "text", + "x": 60, + "y": 264, + "width": 100, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Alice Brown", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "row-4-email", + "type": "text", + "x": 260, + "y": 264, + "width": 140, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "alice@example.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "row-4-role", + "type": "text", + "x": 520, + "y": 264, + "width": 50, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Editor", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "row-4-status-badge", + "type": "rectangle", + "x": 680, + "y": 258, + "width": 60, + "height": 28, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": { + "type": 3, + "value": 14 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-4-status-text", + "type": "text", + "x": 692, + "y": 264, + "width": 36, + "height": 16, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-4-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Active", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 10 + }, + { + "id": "data-row-5", + "type": "rectangle", + "x": 0, + "y": 296, + "width": 1112, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-checkbox", + "type": "rectangle", + "x": 16, + "y": 310, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-name", + "type": "text", + "x": 60, + "y": 312, + "width": 100, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Charlie Davis", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "row-5-email", + "type": "text", + "x": 260, + "y": 312, + "width": 140, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "charlie@example.com", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "row-5-role", + "type": "text", + "x": 520, + "y": 312, + "width": 50, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Viewer", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "row-5-status-badge", + "type": "rectangle", + "x": 680, + "y": 306, + "width": 60, + "height": 28, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 30, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": { + "type": 3, + "value": 14 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "row-5-status-text", + "type": "text", + "x": 692, + "y": 312, + "width": 36, + "height": 16, + "strokeColor": "#f97316", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "row-5-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Active", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 10 + }, + { + "id": "empty-rows-placeholder", + "type": "rectangle", + "x": 0, + "y": 344, + "width": 1112, + "height": 208, + "strokeColor": "transparent", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-bar", + "type": "rectangle", + "x": 0, + "y": 552, + "width": 1112, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "pagination-info", + "type": "text", + "x": 24, + "y": 568, + "width": 140, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Showing 1-10 of 100", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "page-prev", + "type": "rectangle", + "x": 820, + "y": 560, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-prev-arrow", + "type": "text", + "x": 830, + "y": 568, + "width": 12, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "<", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "page-1", + "type": "rectangle", + "x": 860, + "y": 560, + "width": 32, + "height": 32, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-1-text", + "type": "text", + "x": 872, + "y": 568, + "width": 8, + "height": 16, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "1", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "page-2", + "type": "rectangle", + "x": 900, + "y": 560, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-2-text", + "type": "text", + "x": 912, + "y": 568, + "width": 8, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "2", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "page-3", + "type": "rectangle", + "x": 940, + "y": 560, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-3-text", + "type": "text", + "x": 952, + "y": 568, + "width": 8, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "3", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "page-ellipsis", + "type": "text", + "x": 984, + "y": 568, + "width": 20, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "...", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "page-10", + "type": "rectangle", + "x": 1012, + "y": 560, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-10-text", + "type": "text", + "x": 1020, + "y": 568, + "width": 16, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "10", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "page-next", + "type": "rectangle", + "x": 1052, + "y": 560, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "page-next-arrow", + "type": "text", + "x": 1062, + "y": 568, + "width": 12, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "table-group", + "pagination-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": ">", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 12 + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/templates/components/form.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/templates/components/form.excalidraw new file mode 100644 index 0000000..c165d51 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/templates/components/form.excalidraw @@ -0,0 +1,514 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "form-container", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 400, + "height": 500, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "form-title", + "type": "rectangle", + "x": 24, + "y": 24, + "width": 352, + "height": 28, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "form-description", + "type": "rectangle", + "x": 24, + "y": 60, + "width": 352, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "label-1", + "type": "rectangle", + "x": 24, + "y": 100, + "width": 80, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "input-1", + "type": "rectangle", + "x": 24, + "y": 120, + "width": 352, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "label-2", + "type": "rectangle", + "x": 24, + "y": 172, + "width": 80, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "input-2", + "type": "rectangle", + "x": 24, + "y": 192, + "width": 352, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "label-3", + "type": "rectangle", + "x": 24, + "y": 244, + "width": 80, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "input-3", + "type": "rectangle", + "x": 24, + "y": 264, + "width": 352, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "eye-icon", + "type": "rectangle", + "x": 340, + "y": 276, + "width": 20, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "checkbox", + "type": "rectangle", + "x": 24, + "y": 316, + "width": 20, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "checkbox-label", + "type": "rectangle", + "x": 52, + "y": 318, + "width": 100, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "forgot-link", + "type": "rectangle", + "x": 276, + "y": 318, + "width": 100, + "height": 16, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 80, + "groupIds": [], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "primary-button", + "type": "rectangle", + "x": 24, + "y": 352, + "width": 352, + "height": 44, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "secondary-button", + "type": "rectangle", + "x": 24, + "y": 404, + "width": 352, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-left", + "type": "line", + "x": 24, + "y": 468, + "width": 140, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 140, + 0 + ] + ] + }, + { + "id": "divider-text", + "type": "rectangle", + "x": 172, + "y": 460, + "width": 56, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "divider-right", + "type": "line", + "x": 236, + "y": 468, + "width": 140, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 140, + 0 + ] + ] + }, + { + "id": "oauth-google", + "type": "rectangle", + "x": 100, + "y": 484, + "width": 60, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-github", + "type": "rectangle", + "x": 170, + "y": 484, + "width": 60, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "oauth-apple", + "type": "rectangle", + "x": 240, + "y": 484, + "width": 60, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "oauth-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/templates/components/header.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/templates/components/header.excalidraw new file mode 100644 index 0000000..7376b24 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/templates/components/header.excalidraw @@ -0,0 +1,250 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "header-container", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "title-text", + "type": "text", + "x": 24, + "y": 14, + "width": 200, + "height": 24, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group", + "title-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Page Title", + "fontSize": 20, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 18 + }, + { + "id": "description-text", + "type": "text", + "x": 24, + "y": 42, + "width": 300, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group", + "title-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Brief description of this page", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 12 + }, + { + "id": "breadcrumb-area", + "type": "rectangle", + "x": 360, + "y": 22, + "width": 400, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group", + "breadcrumb-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "breadcrumb-text", + "type": "text", + "x": 360, + "y": 22, + "width": 200, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group", + "breadcrumb-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Home > Section > Current", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 10 + }, + { + "id": "help-icon", + "type": "ellipse", + "x": 1000, + "y": 14, + "width": 36, + "height": 36, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group", + "actions-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "help-icon-text", + "type": "text", + "x": 1012, + "y": 22, + "width": 12, + "height": 20, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group", + "actions-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "?", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 14 + }, + { + "id": "action-button", + "type": "rectangle", + "x": 1048, + "y": 14, + "width": 100, + "height": 36, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group", + "actions-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "action-button-text", + "type": "text", + "x": 1068, + "y": 24, + "width": 60, + "height": 16, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group", + "actions-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Action", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "center", + "verticalAlign": "middle", + "baseline": 12 + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/templates/components/modal.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/templates/components/modal.excalidraw new file mode 100644 index 0000000..0d5de1f --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/templates/components/modal.excalidraw @@ -0,0 +1,431 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "backdrop", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 800, + "height": 600, + "strokeColor": "transparent", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 30, + "groupIds": [], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "modal-container", + "type": "rectangle", + "x": 160, + "y": 140, + "width": 480, + "height": 320, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "modal-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-border", + "type": "line", + "x": 160, + "y": 196, + "width": 480, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "modal-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 480, + 0 + ] + ] + }, + { + "id": "modal-title", + "type": "rectangle", + "x": 184, + "y": 160, + "width": 200, + "height": 24, + "strokeColor": "#1a1a1a", + "backgroundColor": "#1a1a1a", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "modal-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "close-button", + "type": "rectangle", + "x": 592, + "y": 156, + "width": 32, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "modal-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "close-x-h", + "type": "line", + "x": 600, + "y": 164, + "width": 16, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "modal-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 16, + 16 + ] + ] + }, + { + "id": "close-x-v", + "type": "line", + "x": 616, + "y": 164, + "width": -16, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 2, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "modal-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + -16, + 16 + ] + ] + }, + { + "id": "content-desc-1", + "type": "rectangle", + "x": 184, + "y": 220, + "width": 432, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "modal-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-desc-2", + "type": "rectangle", + "x": 184, + "y": 244, + "width": 380, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "modal-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-desc-3", + "type": "rectangle", + "x": 184, + "y": 268, + "width": 300, + "height": 16, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 60, + "groupIds": [ + "modal-group" + ], + "roundness": { + "type": 3, + "value": 2 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "optional-input", + "type": "rectangle", + "x": 184, + "y": 304, + "width": 432, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "modal-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "footer-bg", + "type": "rectangle", + "x": 160, + "y": 396, + "width": 480, + "height": 64, + "strokeColor": "transparent", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "modal-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "footer-bg-top-mask", + "type": "rectangle", + "x": 160, + "y": 396, + "width": 480, + "height": 20, + "strokeColor": "transparent", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "modal-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "footer-border", + "type": "line", + "x": 160, + "y": 396, + "width": 480, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "modal-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 480, + 0 + ] + ] + }, + { + "id": "cancel-button", + "type": "rectangle", + "x": 408, + "y": 408, + "width": 100, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "modal-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "confirm-button", + "type": "rectangle", + "x": 516, + "y": 408, + "width": 100, + "height": 40, + "strokeColor": "#f97316", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 0, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "modal-group" + ], + "roundness": { + "type": 3, + "value": 6 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/templates/components/sidebar.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/templates/components/sidebar.excalidraw new file mode 100644 index 0000000..a95097b --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/templates/components/sidebar.excalidraw @@ -0,0 +1,534 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "sidebar-container", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-area", + "type": "rectangle", + "x": 24, + "y": 24, + "width": 232, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-text", + "type": "text", + "x": 44, + "y": 34, + "width": 80, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "logo-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Logo", + "fontSize": 16, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 14 + }, + { + "id": "section-divider-1", + "type": "line", + "x": 24, + "y": 88, + "width": 232, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 232, + 0 + ] + ] + }, + { + "id": "nav-item-1-active", + "type": "rectangle", + "x": 24, + "y": 108, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-item-1-text", + "type": "text", + "x": 44, + "y": 118, + "width": 100, + "height": 20, + "strokeColor": "#ffffff", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Dashboard", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-item-2", + "type": "rectangle", + "x": 24, + "y": 160, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-item-2-text", + "type": "text", + "x": 44, + "y": 170, + "width": 100, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Browse MCPs", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-item-3", + "type": "rectangle", + "x": 24, + "y": 212, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-item-3-text", + "type": "text", + "x": 44, + "y": 222, + "width": 100, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Bundles", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-item-4", + "type": "rectangle", + "x": 24, + "y": 264, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-item-4-text", + "type": "text", + "x": 44, + "y": 274, + "width": 100, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "My Installs", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "nav-item-5", + "type": "rectangle", + "x": 24, + "y": 316, + "width": 232, + "height": 40, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "nav-item-5-text", + "type": "text", + "x": 44, + "y": 326, + "width": 100, + "height": 20, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "nav-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "Settings", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "middle", + "baseline": 12 + }, + { + "id": "section-divider-2", + "type": "line", + "x": 24, + "y": 812, + "width": 232, + "height": 0, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "points": [ + [ + 0, + 0 + ], + [ + 232, + 0 + ] + ] + }, + { + "id": "user-footer", + "type": "rectangle", + "x": 24, + "y": 824, + "width": 232, + "height": 64, + "strokeColor": "transparent", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-avatar", + "type": "ellipse", + "x": 24, + "y": 836, + "width": 40, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "user-name", + "type": "text", + "x": 76, + "y": 840, + "width": 120, + "height": 16, + "strokeColor": "#1a1a1a", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "John Doe", + "fontSize": 14, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 12 + }, + { + "id": "user-email", + "type": "text", + "x": 76, + "y": 858, + "width": 140, + "height": 14, + "strokeColor": "#e5e5e5", + "backgroundColor": "transparent", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group", + "user-group" + ], + "roundness": null, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false, + "text": "john@example.com", + "fontSize": 12, + "fontFamily": 1, + "textAlign": "left", + "verticalAlign": "top", + "baseline": 10 + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/templates/layouts/auth.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/templates/layouts/auth.excalidraw new file mode 100644 index 0000000..5dbc43b --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/templates/layouts/auth.excalidraw @@ -0,0 +1,334 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "left-column", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "left-column-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-column", + "type": "rectangle", + "x": 720, + "y": 0, + "width": 720, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-column-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "logo-placeholder", + "type": "rectangle", + "x": 300, + "y": 80, + "width": 120, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "left-column-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "form-container", + "type": "rectangle", + "x": 160, + "y": 250, + "width": 400, + "height": 400, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "form-title", + "type": "rectangle", + "x": 200, + "y": 290, + "width": 320, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "form-input-1", + "type": "rectangle", + "x": 200, + "y": 360, + "width": 320, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "form-input-2", + "type": "rectangle", + "x": 200, + "y": 420, + "width": 320, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "form-submit-button", + "type": "rectangle", + "x": 200, + "y": 500, + "width": 320, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "form-divider", + "type": "rectangle", + "x": 200, + "y": 564, + "width": 320, + "height": 2, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "form-oauth-button", + "type": "rectangle", + "x": 200, + "y": 586, + "width": 320, + "height": 44, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "form-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-logo", + "type": "rectangle", + "x": 1000, + "y": 400, + "width": 160, + "height": 60, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-column-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "right-branding-tagline", + "type": "rectangle", + "x": 920, + "y": 480, + "width": 320, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "right-column-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/templates/layouts/dashboard.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/templates/layouts/dashboard.excalidraw new file mode 100644 index 0000000..e588463 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/templates/layouts/dashboard.excalidraw @@ -0,0 +1,484 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-area", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-logo-placeholder", + "type": "rectangle", + "x": 20, + "y": 16, + "width": 120, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-1", + "type": "rectangle", + "x": 20, + "y": 100, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-2", + "type": "rectangle", + "x": 20, + "y": 160, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-3", + "type": "rectangle", + "x": 20, + "y": 220, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-nav-item-4", + "type": "rectangle", + "x": 20, + "y": 280, + "width": 240, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer", + "type": "rectangle", + "x": 0, + "y": 836, + "width": 280, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#fafafa", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "sidebar-footer-user", + "type": "rectangle", + "x": 20, + "y": 852, + "width": 240, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "sidebar-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 280, + "y": 0, + "width": 1160, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-title", + "type": "rectangle", + "x": 304, + "y": 20, + "width": 200, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-action-1", + "type": "rectangle", + "x": 1280, + "y": 16, + "width": 60, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-action-2", + "type": "rectangle", + "x": 1360, + "y": 16, + "width": 60, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 280, + "y": 64, + "width": 1160, + "height": 836, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-card-1", + "type": "rectangle", + "x": 304, + "y": 88, + "width": 360, + "height": 180, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-card-2", + "type": "rectangle", + "x": 684, + "y": 88, + "width": 360, + "height": 180, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-card-3", + "type": "rectangle", + "x": 1064, + "y": 88, + "width": 360, + "height": 180, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-main-card", + "type": "rectangle", + "x": 304, + "y": 288, + "width": 1120, + "height": 400, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/themed/templates/layouts/marketing.excalidraw b/.context/turbostarter-framework-context/wireframes/themed/templates/layouts/marketing.excalidraw new file mode 100644 index 0000000..6c3d458 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/themed/templates/layouts/marketing.excalidraw @@ -0,0 +1,559 @@ +{ + "type": "excalidraw", + "version": 2, + "source": "turbostarter-wireframes", + "elements": [ + { + "id": "outer-frame", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 900, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header", + "type": "rectangle", + "x": 0, + "y": 0, + "width": 1440, + "height": 64, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-logo", + "type": "rectangle", + "x": 40, + "y": 16, + "width": 100, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-nav-1", + "type": "rectangle", + "x": 580, + "y": 20, + "width": 80, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-nav-2", + "type": "rectangle", + "x": 680, + "y": 20, + "width": 80, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-nav-3", + "type": "rectangle", + "x": 780, + "y": 20, + "width": 80, + "height": 24, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-auth-login", + "type": "rectangle", + "x": 1260, + "y": 16, + "width": 80, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "header-auth-signup", + "type": "rectangle", + "x": 1360, + "y": 16, + "width": 80, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "header-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "content-area", + "type": "rectangle", + "x": 0, + "y": 64, + "width": 1440, + "height": 716, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "hero-headline", + "type": "rectangle", + "x": 320, + "y": 160, + "width": 800, + "height": 60, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "hero-subheadline", + "type": "rectangle", + "x": 420, + "y": 240, + "width": 600, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "hero-cta-primary", + "type": "rectangle", + "x": 560, + "y": 320, + "width": 160, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "hero-cta-secondary", + "type": "rectangle", + "x": 740, + "y": 320, + "width": 140, + "height": 48, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "hero-image", + "type": "rectangle", + "x": 220, + "y": 420, + "width": 1000, + "height": 320, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "content-group" + ], + "roundness": { + "type": 3, + "value": 12 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "footer", + "type": "rectangle", + "x": 0, + "y": 780, + "width": 1440, + "height": 120, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f5f5f5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "footer-group" + ], + "roundness": { + "type": 3, + "value": 0 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "footer-logo", + "type": "rectangle", + "x": 40, + "y": 800, + "width": 100, + "height": 32, + "strokeColor": "#e5e5e5", + "backgroundColor": "#f97316", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "footer-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "footer-links-1", + "type": "rectangle", + "x": 400, + "y": 800, + "width": 140, + "height": 80, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "footer-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "footer-links-2", + "type": "rectangle", + "x": 560, + "y": 800, + "width": 140, + "height": 80, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "footer-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "footer-links-3", + "type": "rectangle", + "x": 720, + "y": 800, + "width": 140, + "height": 80, + "strokeColor": "#e5e5e5", + "backgroundColor": "#e5e5e5", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "footer-group" + ], + "roundness": { + "type": 3, + "value": 4 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "footer-social-1", + "type": "rectangle", + "x": 1280, + "y": 820, + "width": 40, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "footer-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "footer-social-2", + "type": "rectangle", + "x": 1340, + "y": 820, + "width": 40, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "footer-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + }, + { + "id": "footer-social-3", + "type": "rectangle", + "x": 1400, + "y": 820, + "width": 40, + "height": 40, + "strokeColor": "#e5e5e5", + "backgroundColor": "#ffffff", + "fillStyle": "solid", + "strokeWidth": 1, + "roughness": 0, + "opacity": 100, + "groupIds": [ + "footer-group" + ], + "roundness": { + "type": 3, + "value": 8 + }, + "isDeleted": false, + "boundElements": null, + "link": null, + "locked": false + } + ], + "appState": { + "gridSize": 20, + "viewBackgroundColor": "$background" + } +} \ No newline at end of file diff --git a/.context/turbostarter-framework-context/wireframes/wireframe-themes.json b/.context/turbostarter-framework-context/wireframes/wireframe-themes.json new file mode 100644 index 0000000..b0177f4 --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/wireframe-themes.json @@ -0,0 +1,172 @@ +{ + "themes": { + "orange-light": { + "background": "#ffffff", + "foreground": "#1a1a1a", + "primary": "#f97316", + "primary-foreground": "#ffffff", + "secondary": "#f5f5f5", + "secondary-foreground": "#1a1a1a", + "muted": "#e5e5e5", + "muted-foreground": "#737373", + "accent": "#f5f5f5", + "accent-foreground": "#1a1a1a", + "border": "#e5e5e5", + "card": "#ffffff", + "card-foreground": "#1a1a1a", + "destructive": "#ef4444", + "destructive-foreground": "#ffffff", + "success": "#22c55e", + "success-foreground": "#ffffff", + "sidebar": "#fafafa", + "sidebar-foreground": "#1a1a1a" + }, + "orange-dark": { + "background": "#1a1a1a", + "foreground": "#fafafa", + "primary": "#f97316", + "primary-foreground": "#ffffff", + "secondary": "#2a2a2a", + "secondary-foreground": "#fafafa", + "muted": "#3a3a3a", + "muted-foreground": "#a1a1a1", + "accent": "#2a2a2a", + "accent-foreground": "#fafafa", + "border": "#3a3a3a", + "card": "#2a2a2a", + "card-foreground": "#fafafa", + "destructive": "#ef4444", + "destructive-foreground": "#ffffff", + "success": "#22c55e", + "success-foreground": "#ffffff", + "sidebar": "#2a2a2a", + "sidebar-foreground": "#fafafa" + }, + "blue-light": { + "background": "#ffffff", + "foreground": "#1a1a1a", + "primary": "#2563eb", + "primary-foreground": "#ffffff", + "secondary": "#f5f5f5", + "secondary-foreground": "#1a1a1a", + "muted": "#e5e5e5", + "muted-foreground": "#737373", + "accent": "#f5f5f5", + "accent-foreground": "#1a1a1a", + "border": "#e5e5e5", + "card": "#ffffff", + "card-foreground": "#1a1a1a", + "destructive": "#ef4444", + "destructive-foreground": "#ffffff", + "success": "#22c55e", + "success-foreground": "#ffffff", + "sidebar": "#fafafa", + "sidebar-foreground": "#1a1a1a" + }, + "blue-dark": { + "background": "#1a1a1a", + "foreground": "#fafafa", + "primary": "#3b82f6", + "primary-foreground": "#ffffff", + "secondary": "#2a2a2a", + "secondary-foreground": "#fafafa", + "muted": "#3a3a3a", + "muted-foreground": "#a1a1a1", + "accent": "#2a2a2a", + "accent-foreground": "#fafafa", + "border": "#3a3a3a", + "card": "#2a2a2a", + "card-foreground": "#fafafa", + "destructive": "#ef4444", + "destructive-foreground": "#ffffff", + "success": "#22c55e", + "success-foreground": "#ffffff", + "sidebar": "#2a2a2a", + "sidebar-foreground": "#fafafa" + }, + "green-light": { + "background": "#ffffff", + "foreground": "#1a1a1a", + "primary": "#16a34a", + "primary-foreground": "#ffffff", + "secondary": "#f5f5f5", + "secondary-foreground": "#1a1a1a", + "muted": "#e5e5e5", + "muted-foreground": "#737373", + "accent": "#f5f5f5", + "accent-foreground": "#1a1a1a", + "border": "#e5e5e5", + "card": "#ffffff", + "card-foreground": "#1a1a1a", + "destructive": "#ef4444", + "destructive-foreground": "#ffffff", + "success": "#22c55e", + "success-foreground": "#ffffff", + "sidebar": "#fafafa", + "sidebar-foreground": "#1a1a1a" + }, + "green-dark": { + "background": "#1a1a1a", + "foreground": "#fafafa", + "primary": "#22c55e", + "primary-foreground": "#ffffff", + "secondary": "#2a2a2a", + "secondary-foreground": "#fafafa", + "muted": "#3a3a3a", + "muted-foreground": "#a1a1a1", + "accent": "#2a2a2a", + "accent-foreground": "#fafafa", + "border": "#3a3a3a", + "card": "#2a2a2a", + "card-foreground": "#fafafa", + "destructive": "#ef4444", + "destructive-foreground": "#ffffff", + "success": "#22c55e", + "success-foreground": "#ffffff", + "sidebar": "#2a2a2a", + "sidebar-foreground": "#fafafa" + }, + "violet-light": { + "background": "#ffffff", + "foreground": "#1a1a1a", + "primary": "#7c3aed", + "primary-foreground": "#ffffff", + "secondary": "#f5f5f5", + "secondary-foreground": "#1a1a1a", + "muted": "#e5e5e5", + "muted-foreground": "#737373", + "accent": "#f5f5f5", + "accent-foreground": "#1a1a1a", + "border": "#e5e5e5", + "card": "#ffffff", + "card-foreground": "#1a1a1a", + "destructive": "#ef4444", + "destructive-foreground": "#ffffff", + "success": "#22c55e", + "success-foreground": "#ffffff", + "sidebar": "#fafafa", + "sidebar-foreground": "#1a1a1a" + }, + "violet-dark": { + "background": "#1a1a1a", + "foreground": "#fafafa", + "primary": "#8b5cf6", + "primary-foreground": "#ffffff", + "secondary": "#2a2a2a", + "secondary-foreground": "#fafafa", + "muted": "#3a3a3a", + "muted-foreground": "#a1a1a1", + "accent": "#2a2a2a", + "accent-foreground": "#fafafa", + "border": "#3a3a3a", + "card": "#2a2a2a", + "card-foreground": "#fafafa", + "destructive": "#ef4444", + "destructive-foreground": "#ffffff", + "success": "#22c55e", + "success-foreground": "#ffffff", + "sidebar": "#2a2a2a", + "sidebar-foreground": "#fafafa" + } + } +} diff --git a/.context/turbostarter-framework-context/wireframes/wireframe-theming.md b/.context/turbostarter-framework-context/wireframes/wireframe-theming.md new file mode 100644 index 0000000..49b69ad --- /dev/null +++ b/.context/turbostarter-framework-context/wireframes/wireframe-theming.md @@ -0,0 +1,276 @@ +# TurboStarter Wireframe Theming System + +## Overview + +The TurboStarter wireframe theming system uses **token-based color references** that allow Excalidraw wireframes to be themed dynamically. Instead of hardcoding hex colors directly in Excalidraw files, designers use semantic tokens like `$background`, `$primary`, etc. These tokens are later replaced with actual colors using the `apply-theme.js` script. + +### How It Works + +1. **Create wireframes using tokens** - Use `$tokenName` syntax for colors in Excalidraw's `strokeColor` and `backgroundColor` properties +2. **Store the template** - Save the `.excalidraw` file with tokens intact +3. **Apply a theme** - Run `apply-theme.js` to generate a themed version with real hex colors +4. **Multiple outputs** - Generate different themed versions from the same template + +This approach enables: +- Single source of truth for wireframes +- Consistent branding across all diagrams +- Easy theme switching without manual color updates +- Light/dark mode variants from the same template + +--- + +## Token Reference + +| Token | Usage | Light Mode Example | Dark Mode Example | +|-------|-------|-------------------|-------------------| +| `$background` | Page/screen background | `#ffffff` (white) | `#1a1a1a` (near black) | +| `$foreground` | Primary text color | `#1a1a1a` (dark gray) | `#fafafa` (near white) | +| `$primary` | Brand color, CTAs, active states | Varies by theme (e.g., `#e85d04` orange) | Same or adjusted | +| `$secondary` | Secondary backgrounds, subtle fills | `#f5f5f5` (light gray) | `#262626` (dark gray) | +| `$muted` | Disabled states, placeholders, subtle text | `#f5f5f5` (light gray) | `#262626` (dark gray) | +| `$border` | Borders, dividers, outlines | `#e5e5e5` (gray) | `#404040` (medium gray) | +| `$card` | Card and panel backgrounds | `#ffffff` (white) | `#1f1f1f` (dark) | +| `$destructive` | Delete buttons, error states, warnings | `#ef4444` (red) | `#ef4444` (red) | +| `$success` | Success states, confirmations | `#22c55e` (green) | `#22c55e` (green) | +| `$sidebar` | Sidebar background | `#fafafa` (off-white) | `#171717` (darker) | +| `$sidebar-foreground` | Sidebar text | `#1a1a1a` (dark) | `#fafafa` (light) | + +### Token Naming Convention + +- Tokens always start with `$` prefix +- Names match TurboStarter CSS variable names +- Use kebab-case for multi-word tokens + +--- + +## Available Themes + +The system includes **18 themes** (9 color palettes x 2 modes): + +### Color Palettes + +| Color | Light Theme | Dark Theme | Primary Color | +|-------|-------------|------------|---------------| +| Orange | `orange-light` | `orange-dark` | `#e85d04` | +| Blue | `blue-light` | `blue-dark` | `#2563eb` | +| Green | `green-light` | `green-dark` | `#16a34a` | +| Red | `red-light` | `red-dark` | `#dc2626` | +| Rose | `rose-light` | `rose-dark` | `#e11d48` | +| Violet | `violet-light` | `violet-dark` | `#7c3aed` | +| Yellow | `yellow-light` | `yellow-dark` | `#eab308` | +| Gray | `gray-light` | `gray-dark` | `#374151` / `#6b7280` | +| Stone | `stone-light` | `stone-dark` | `#44403c` / `#78716c` | + +### Theme Structure + +Each theme defines all 11 tokens. Example for `orange-light`: + +```json +{ + "$background": "#ffffff", + "$foreground": "#1a1a1a", + "$primary": "#e85d04", + "$secondary": "#f5f5f5", + "$muted": "#f5f5f5", + "$border": "#e5e5e5", + "$card": "#ffffff", + "$destructive": "#ef4444", + "$success": "#22c55e", + "$sidebar": "#fafafa", + "$sidebar-foreground": "#1a1a1a" +} +``` + +--- + +## How to Apply Themes + +### Prerequisites + +- Node.js installed +- `wireframe-themes.json` in the same directory as `apply-theme.js` + +### Basic Usage + +```bash +node apply-theme.js [output.excalidraw] +``` + +### Examples + +```bash +# Apply orange-light theme, auto-generate output filename +node apply-theme.js wireframe.excalidraw orange-light +# Output: wireframe-orange-light.excalidraw + +# Apply blue-dark theme with custom output +node apply-theme.js wireframe.excalidraw blue-dark themed-wireframe.excalidraw + +# Apply theme with verbose output +node apply-theme.js wireframe.excalidraw violet-light --verbose +``` + +### Generating Multiple Themes + +```bash +# Generate all light themes +for theme in orange blue green red rose violet yellow gray stone; do + node apply-theme.js wireframe.excalidraw ${theme}-light +done + +# Generate light and dark for one color +node apply-theme.js wireframe.excalidraw orange-light +node apply-theme.js wireframe.excalidraw orange-dark +``` + +--- + +## For AI Assistants + +### Quick Reference + +When creating or modifying Excalidraw wireframes: + +1. **Always use tokens** - Never hardcode hex colors for themeable elements +2. **Token format** - Use `$tokenName` in `strokeColor` and `backgroundColor` fields +3. **Common patterns:** + - Page background: `"backgroundColor": "$background"` + - Text/icons: `"strokeColor": "$foreground"` + - Buttons/CTAs: `"backgroundColor": "$primary"` + `"strokeColor": "$primary"` + - Cards/panels: `"backgroundColor": "$card"` + `"strokeColor": "$border"` + - Input fields: `"backgroundColor": "$background"` + `"strokeColor": "$border"` + - Disabled elements: `"backgroundColor": "$muted"` + `"strokeColor": "$muted"` + - Error states: `"strokeColor": "$destructive"` + - Success states: `"strokeColor": "$success"` + +### Example Element + +```json +{ + "type": "rectangle", + "id": "button-cta", + "x": 100, + "y": 200, + "width": 120, + "height": 40, + "strokeColor": "$primary", + "backgroundColor": "$primary", + "fillStyle": "solid", + "strokeWidth": 2, + "roundness": { "type": 3, "value": 4 } +} +``` + +### Workflow + +1. Create wireframe using `$tokens` for all colors +2. Save as `.excalidraw` file +3. Run `apply-theme.js` to generate themed versions +4. Use themed output in documentation or presentations + +--- + +## Color Mapping to TurboStarter CSS + +Wireframe tokens map directly to TurboStarter's Tailwind CSS variables: + +| Wireframe Token | CSS Variable | Tailwind Class | +|-----------------|--------------|----------------| +| `$background` | `--background` | `bg-background` | +| `$foreground` | `--foreground` | `text-foreground` | +| `$primary` | `--primary` | `bg-primary`, `text-primary` | +| `$secondary` | `--secondary` | `bg-secondary` | +| `$muted` | `--muted` | `bg-muted`, `text-muted-foreground` | +| `$border` | `--border` | `border-border` | +| `$card` | `--card` | `bg-card` | +| `$destructive` | `--destructive` | `bg-destructive`, `text-destructive` | +| `$success` | `--success` | `bg-success`, `text-success` | +| `$sidebar` | `--sidebar` | `bg-sidebar` | +| `$sidebar-foreground` | `--sidebar-foreground` | `text-sidebar-foreground` | + +### CSS Variable Definition (TurboStarter) + +TurboStarter defines these in `globals.css`: + +```css +@theme inline { + --color-background: var(--background); + --color-foreground: var(--foreground); + --color-primary: var(--primary); + --color-secondary: var(--secondary); + --color-muted: var(--muted); + --color-border: var(--border); + --color-card: var(--card); + --color-destructive: var(--destructive); + --color-success: var(--success); + --color-sidebar: var(--sidebar); + --color-sidebar-foreground: var(--sidebar-foreground); + /* ... additional variables */ +} +``` + +--- + +## File Locations + +| File | Path | Purpose | +|------|------|---------| +| Theme definitions | `_bmad-output/excalidraw-diagrams/wireframe-themes.json` | All 18 theme color mappings | +| Theme applicator | `_bmad-output/excalidraw-diagrams/apply-theme.js` | Script to apply themes | +| Template storage | `_bmad-output/excalidraw-diagrams/` | Store wireframe templates here | +| TurboStarter themes | `packages/ui/shared/src/styles/themes/` | Source theme definitions (OKLCH) | + +--- + +## Extending the System + +### Adding Custom Tokens + +1. Add the token to `wireframe-themes.json` under each theme +2. Use the new token in wireframes as `$token-name` + +### Adding Custom Themes + +Add a new entry to `wireframe-themes.json`: + +```json +{ + "themes": { + "custom-brand-light": { + "$background": "#ffffff", + "$foreground": "#1a1a1a", + "$primary": "#your-brand-color", + "$secondary": "#f5f5f5", + "$muted": "#f5f5f5", + "$border": "#e5e5e5", + "$card": "#ffffff", + "$destructive": "#ef4444", + "$success": "#22c55e", + "$sidebar": "#fafafa", + "$sidebar-foreground": "#1a1a1a" + } + } +} +``` + +--- + +## Troubleshooting + +### Token Not Replaced + +- Verify the token name matches exactly (case-sensitive) +- Ensure the token includes the `$` prefix +- Check that the theme includes the token in `wireframe-themes.json` + +### Colors Look Wrong + +- Confirm you're using the correct theme name (light vs dark) +- Verify the `wireframe-themes.json` file is up to date +- Check for typos in token names + +### Script Errors + +- Ensure `wireframe-themes.json` is valid JSON +- Verify the input `.excalidraw` file is valid JSON +- Check Node.js is installed and accessible diff --git a/.cursor/rules/bmad/bmm/agents/analyst.mdc b/.cursor/rules/bmad/bmm/agents/analyst.mdc new file mode 100644 index 0000000..dc6d840 --- /dev/null +++ b/.cursor/rules/bmad/bmm/agents/analyst.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD BMM Agent: analyst +globs: +alwaysApply: false +--- + +You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command. + + +1. LOAD the FULL agent file from @_bmad/bmm/agents/analyst.md +2. READ its entire contents - this contains the complete agent persona, menu, and instructions +3. Execute ALL activation steps exactly as written in the agent file +4. Follow the agent's persona and menu system precisely +5. Stay in character throughout the session + diff --git a/.cursor/rules/bmad/bmm/agents/architect.mdc b/.cursor/rules/bmad/bmm/agents/architect.mdc new file mode 100644 index 0000000..d2e75b1 --- /dev/null +++ b/.cursor/rules/bmad/bmm/agents/architect.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD BMM Agent: architect +globs: +alwaysApply: false +--- + +You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command. + + +1. LOAD the FULL agent file from @_bmad/bmm/agents/architect.md +2. READ its entire contents - this contains the complete agent persona, menu, and instructions +3. Execute ALL activation steps exactly as written in the agent file +4. Follow the agent's persona and menu system precisely +5. Stay in character throughout the session + diff --git a/.cursor/rules/bmad/bmm/agents/dev.mdc b/.cursor/rules/bmad/bmm/agents/dev.mdc new file mode 100644 index 0000000..7225de9 --- /dev/null +++ b/.cursor/rules/bmad/bmm/agents/dev.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD BMM Agent: dev +globs: +alwaysApply: false +--- + +You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command. + + +1. LOAD the FULL agent file from @_bmad/bmm/agents/dev.md +2. READ its entire contents - this contains the complete agent persona, menu, and instructions +3. Execute ALL activation steps exactly as written in the agent file +4. Follow the agent's persona and menu system precisely +5. Stay in character throughout the session + diff --git a/.cursor/rules/bmad/bmm/agents/pm.mdc b/.cursor/rules/bmad/bmm/agents/pm.mdc new file mode 100644 index 0000000..71dd92e --- /dev/null +++ b/.cursor/rules/bmad/bmm/agents/pm.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD BMM Agent: pm +globs: +alwaysApply: false +--- + +You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command. + + +1. LOAD the FULL agent file from @_bmad/bmm/agents/pm.md +2. READ its entire contents - this contains the complete agent persona, menu, and instructions +3. Execute ALL activation steps exactly as written in the agent file +4. Follow the agent's persona and menu system precisely +5. Stay in character throughout the session + diff --git a/.cursor/rules/bmad/bmm/agents/quick-flow-solo-dev.mdc b/.cursor/rules/bmad/bmm/agents/quick-flow-solo-dev.mdc new file mode 100644 index 0000000..44406d4 --- /dev/null +++ b/.cursor/rules/bmad/bmm/agents/quick-flow-solo-dev.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD BMM Agent: quick-flow-solo-dev +globs: +alwaysApply: false +--- + +You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command. + + +1. LOAD the FULL agent file from @_bmad/bmm/agents/quick-flow-solo-dev.md +2. READ its entire contents - this contains the complete agent persona, menu, and instructions +3. Execute ALL activation steps exactly as written in the agent file +4. Follow the agent's persona and menu system precisely +5. Stay in character throughout the session + diff --git a/.cursor/rules/bmad/bmm/agents/sm.mdc b/.cursor/rules/bmad/bmm/agents/sm.mdc new file mode 100644 index 0000000..e041237 --- /dev/null +++ b/.cursor/rules/bmad/bmm/agents/sm.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD BMM Agent: sm +globs: +alwaysApply: false +--- + +You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command. + + +1. LOAD the FULL agent file from @_bmad/bmm/agents/sm.md +2. READ its entire contents - this contains the complete agent persona, menu, and instructions +3. Execute ALL activation steps exactly as written in the agent file +4. Follow the agent's persona and menu system precisely +5. Stay in character throughout the session + diff --git a/.cursor/rules/bmad/bmm/agents/tea.mdc b/.cursor/rules/bmad/bmm/agents/tea.mdc new file mode 100644 index 0000000..ad8d8e8 --- /dev/null +++ b/.cursor/rules/bmad/bmm/agents/tea.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD BMM Agent: tea +globs: +alwaysApply: false +--- + +You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command. + + +1. LOAD the FULL agent file from @_bmad/bmm/agents/tea.md +2. READ its entire contents - this contains the complete agent persona, menu, and instructions +3. Execute ALL activation steps exactly as written in the agent file +4. Follow the agent's persona and menu system precisely +5. Stay in character throughout the session + diff --git a/.cursor/rules/bmad/bmm/agents/tech-writer.mdc b/.cursor/rules/bmad/bmm/agents/tech-writer.mdc new file mode 100644 index 0000000..58870ca --- /dev/null +++ b/.cursor/rules/bmad/bmm/agents/tech-writer.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD BMM Agent: tech-writer +globs: +alwaysApply: false +--- + +You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command. + + +1. LOAD the FULL agent file from @_bmad/bmm/agents/tech-writer.md +2. READ its entire contents - this contains the complete agent persona, menu, and instructions +3. Execute ALL activation steps exactly as written in the agent file +4. Follow the agent's persona and menu system precisely +5. Stay in character throughout the session + diff --git a/.cursor/rules/bmad/bmm/agents/ux-designer.mdc b/.cursor/rules/bmad/bmm/agents/ux-designer.mdc new file mode 100644 index 0000000..666c13b --- /dev/null +++ b/.cursor/rules/bmad/bmm/agents/ux-designer.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD BMM Agent: ux-designer +globs: +alwaysApply: false +--- + +You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command. + + +1. LOAD the FULL agent file from @_bmad/bmm/agents/ux-designer.md +2. READ its entire contents - this contains the complete agent persona, menu, and instructions +3. Execute ALL activation steps exactly as written in the agent file +4. Follow the agent's persona and menu system precisely +5. Stay in character throughout the session + diff --git a/.cursor/rules/bmad/bmm/workflows/check-implementation-readiness.mdc b/.cursor/rules/bmad/bmm/workflows/check-implementation-readiness.mdc new file mode 100644 index 0000000..08a239d --- /dev/null +++ b/.cursor/rules/bmad/bmm/workflows/check-implementation-readiness.mdc @@ -0,0 +1,7 @@ +--- +description: BMAD BMM Agent: check-implementation-readiness +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL @_bmad/bmm/workflows/3-solutioning/check-implementation-readiness/workflow.md, READ its entire contents and follow its directions exactly! diff --git a/.cursor/rules/bmad/bmm/workflows/code-review.mdc b/.cursor/rules/bmad/bmm/workflows/code-review.mdc new file mode 100644 index 0000000..94f37f9 --- /dev/null +++ b/.cursor/rules/bmad/bmm/workflows/code-review.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD BMM Agent: code-review +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded: + + +1. Always LOAD the FULL @_bmad/core/tasks/workflow.xml +2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @_bmad/bmm/workflows/4-implementation/code-review/workflow.yaml +3. Pass the yaml path _bmad/bmm/workflows/4-implementation/code-review/workflow.yaml as 'workflow-config' parameter to the workflow.xml instructions +4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions +5. Save outputs after EACH section when generating any documents from templates + diff --git a/.cursor/rules/bmad/bmm/workflows/correct-course.mdc b/.cursor/rules/bmad/bmm/workflows/correct-course.mdc new file mode 100644 index 0000000..c5f2ab8 --- /dev/null +++ b/.cursor/rules/bmad/bmm/workflows/correct-course.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD BMM Agent: correct-course +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded: + + +1. Always LOAD the FULL @_bmad/core/tasks/workflow.xml +2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @_bmad/bmm/workflows/4-implementation/correct-course/workflow.yaml +3. Pass the yaml path _bmad/bmm/workflows/4-implementation/correct-course/workflow.yaml as 'workflow-config' parameter to the workflow.xml instructions +4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions +5. Save outputs after EACH section when generating any documents from templates + diff --git a/.cursor/rules/bmad/bmm/workflows/create-architecture.mdc b/.cursor/rules/bmad/bmm/workflows/create-architecture.mdc new file mode 100644 index 0000000..03185ec --- /dev/null +++ b/.cursor/rules/bmad/bmm/workflows/create-architecture.mdc @@ -0,0 +1,7 @@ +--- +description: BMAD BMM Agent: create-architecture +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL @_bmad/bmm/workflows/3-solutioning/create-architecture/workflow.md, READ its entire contents and follow its directions exactly! diff --git a/.cursor/rules/bmad/bmm/workflows/create-epics-and-stories.mdc b/.cursor/rules/bmad/bmm/workflows/create-epics-and-stories.mdc new file mode 100644 index 0000000..e70e4f7 --- /dev/null +++ b/.cursor/rules/bmad/bmm/workflows/create-epics-and-stories.mdc @@ -0,0 +1,7 @@ +--- +description: BMAD BMM Agent: create-epics-and-stories +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL @_bmad/bmm/workflows/3-solutioning/create-epics-and-stories/workflow.md, READ its entire contents and follow its directions exactly! diff --git a/.cursor/rules/bmad/bmm/workflows/create-excalidraw-dataflow.mdc b/.cursor/rules/bmad/bmm/workflows/create-excalidraw-dataflow.mdc new file mode 100644 index 0000000..b23293d --- /dev/null +++ b/.cursor/rules/bmad/bmm/workflows/create-excalidraw-dataflow.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD BMM Agent: create-excalidraw-dataflow +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded: + + +1. Always LOAD the FULL @_bmad/core/tasks/workflow.xml +2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @_bmad/bmm/workflows/excalidraw-diagrams/create-dataflow/workflow.yaml +3. Pass the yaml path _bmad/bmm/workflows/excalidraw-diagrams/create-dataflow/workflow.yaml as 'workflow-config' parameter to the workflow.xml instructions +4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions +5. Save outputs after EACH section when generating any documents from templates + diff --git a/.cursor/rules/bmad/bmm/workflows/create-excalidraw-diagram.mdc b/.cursor/rules/bmad/bmm/workflows/create-excalidraw-diagram.mdc new file mode 100644 index 0000000..fb0e6f4 --- /dev/null +++ b/.cursor/rules/bmad/bmm/workflows/create-excalidraw-diagram.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD BMM Agent: create-excalidraw-diagram +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded: + + +1. Always LOAD the FULL @_bmad/core/tasks/workflow.xml +2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @_bmad/bmm/workflows/excalidraw-diagrams/create-diagram/workflow.yaml +3. Pass the yaml path _bmad/bmm/workflows/excalidraw-diagrams/create-diagram/workflow.yaml as 'workflow-config' parameter to the workflow.xml instructions +4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions +5. Save outputs after EACH section when generating any documents from templates + diff --git a/.cursor/rules/bmad/bmm/workflows/create-excalidraw-flowchart.mdc b/.cursor/rules/bmad/bmm/workflows/create-excalidraw-flowchart.mdc new file mode 100644 index 0000000..20f6d67 --- /dev/null +++ b/.cursor/rules/bmad/bmm/workflows/create-excalidraw-flowchart.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD BMM Agent: create-excalidraw-flowchart +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded: + + +1. Always LOAD the FULL @_bmad/core/tasks/workflow.xml +2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @_bmad/bmm/workflows/excalidraw-diagrams/create-flowchart/workflow.yaml +3. Pass the yaml path _bmad/bmm/workflows/excalidraw-diagrams/create-flowchart/workflow.yaml as 'workflow-config' parameter to the workflow.xml instructions +4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions +5. Save outputs after EACH section when generating any documents from templates + diff --git a/.cursor/rules/bmad/bmm/workflows/create-excalidraw-wireframe.mdc b/.cursor/rules/bmad/bmm/workflows/create-excalidraw-wireframe.mdc new file mode 100644 index 0000000..da3b67b --- /dev/null +++ b/.cursor/rules/bmad/bmm/workflows/create-excalidraw-wireframe.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD BMM Agent: create-excalidraw-wireframe +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded: + + +1. Always LOAD the FULL @_bmad/core/tasks/workflow.xml +2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @_bmad/bmm/workflows/excalidraw-diagrams/create-wireframe/workflow.yaml +3. Pass the yaml path _bmad/bmm/workflows/excalidraw-diagrams/create-wireframe/workflow.yaml as 'workflow-config' parameter to the workflow.xml instructions +4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions +5. Save outputs after EACH section when generating any documents from templates + diff --git a/.cursor/rules/bmad/bmm/workflows/create-prd.mdc b/.cursor/rules/bmad/bmm/workflows/create-prd.mdc new file mode 100644 index 0000000..0e53ccb --- /dev/null +++ b/.cursor/rules/bmad/bmm/workflows/create-prd.mdc @@ -0,0 +1,7 @@ +--- +description: BMAD BMM Agent: create-prd +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL @_bmad/bmm/workflows/2-plan-workflows/prd/workflow.md, READ its entire contents and follow its directions exactly! diff --git a/.cursor/rules/bmad/bmm/workflows/create-product-brief.mdc b/.cursor/rules/bmad/bmm/workflows/create-product-brief.mdc new file mode 100644 index 0000000..cf6d2be --- /dev/null +++ b/.cursor/rules/bmad/bmm/workflows/create-product-brief.mdc @@ -0,0 +1,7 @@ +--- +description: BMAD BMM Agent: create-product-brief +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL @_bmad/bmm/workflows/1-analysis/create-product-brief/workflow.md, READ its entire contents and follow its directions exactly! diff --git a/.cursor/rules/bmad/bmm/workflows/create-story.mdc b/.cursor/rules/bmad/bmm/workflows/create-story.mdc new file mode 100644 index 0000000..68e59fe --- /dev/null +++ b/.cursor/rules/bmad/bmm/workflows/create-story.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD BMM Agent: create-story +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded: + + +1. Always LOAD the FULL @_bmad/core/tasks/workflow.xml +2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @_bmad/bmm/workflows/4-implementation/create-story/workflow.yaml +3. Pass the yaml path _bmad/bmm/workflows/4-implementation/create-story/workflow.yaml as 'workflow-config' parameter to the workflow.xml instructions +4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions +5. Save outputs after EACH section when generating any documents from templates + diff --git a/.cursor/rules/bmad/bmm/workflows/create-tech-spec.mdc b/.cursor/rules/bmad/bmm/workflows/create-tech-spec.mdc new file mode 100644 index 0000000..7bc4c48 --- /dev/null +++ b/.cursor/rules/bmad/bmm/workflows/create-tech-spec.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD BMM Agent: create-tech-spec +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded: + + +1. Always LOAD the FULL @_bmad/core/tasks/workflow.xml +2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @_bmad/bmm/workflows/bmad-quick-flow/create-tech-spec/workflow.yaml +3. Pass the yaml path _bmad/bmm/workflows/bmad-quick-flow/create-tech-spec/workflow.yaml as 'workflow-config' parameter to the workflow.xml instructions +4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions +5. Save outputs after EACH section when generating any documents from templates + diff --git a/.cursor/rules/bmad/bmm/workflows/create-ux-design.mdc b/.cursor/rules/bmad/bmm/workflows/create-ux-design.mdc new file mode 100644 index 0000000..f5ab667 --- /dev/null +++ b/.cursor/rules/bmad/bmm/workflows/create-ux-design.mdc @@ -0,0 +1,7 @@ +--- +description: BMAD BMM Agent: create-ux-design +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL @_bmad/bmm/workflows/2-plan-workflows/create-ux-design/workflow.md, READ its entire contents and follow its directions exactly! diff --git a/.cursor/rules/bmad/bmm/workflows/dev-story.mdc b/.cursor/rules/bmad/bmm/workflows/dev-story.mdc new file mode 100644 index 0000000..a2162c8 --- /dev/null +++ b/.cursor/rules/bmad/bmm/workflows/dev-story.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD BMM Agent: dev-story +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded: + + +1. Always LOAD the FULL @_bmad/core/tasks/workflow.xml +2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @_bmad/bmm/workflows/4-implementation/dev-story/workflow.yaml +3. Pass the yaml path _bmad/bmm/workflows/4-implementation/dev-story/workflow.yaml as 'workflow-config' parameter to the workflow.xml instructions +4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions +5. Save outputs after EACH section when generating any documents from templates + diff --git a/.cursor/rules/bmad/bmm/workflows/document-project.mdc b/.cursor/rules/bmad/bmm/workflows/document-project.mdc new file mode 100644 index 0000000..d2b95bc --- /dev/null +++ b/.cursor/rules/bmad/bmm/workflows/document-project.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD BMM Agent: document-project +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded: + + +1. Always LOAD the FULL @_bmad/core/tasks/workflow.xml +2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @_bmad/bmm/workflows/document-project/workflow.yaml +3. Pass the yaml path _bmad/bmm/workflows/document-project/workflow.yaml as 'workflow-config' parameter to the workflow.xml instructions +4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions +5. Save outputs after EACH section when generating any documents from templates + diff --git a/.cursor/rules/bmad/bmm/workflows/generate-project-context.mdc b/.cursor/rules/bmad/bmm/workflows/generate-project-context.mdc new file mode 100644 index 0000000..38c36c4 --- /dev/null +++ b/.cursor/rules/bmad/bmm/workflows/generate-project-context.mdc @@ -0,0 +1,7 @@ +--- +description: BMAD BMM Agent: generate-project-context +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL @_bmad/bmm/workflows/generate-project-context/workflow.md, READ its entire contents and follow its directions exactly! diff --git a/.cursor/rules/bmad/bmm/workflows/quick-dev.mdc b/.cursor/rules/bmad/bmm/workflows/quick-dev.mdc new file mode 100644 index 0000000..032088b --- /dev/null +++ b/.cursor/rules/bmad/bmm/workflows/quick-dev.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD BMM Agent: quick-dev +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded: + + +1. Always LOAD the FULL @_bmad/core/tasks/workflow.xml +2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @_bmad/bmm/workflows/bmad-quick-flow/quick-dev/workflow.yaml +3. Pass the yaml path _bmad/bmm/workflows/bmad-quick-flow/quick-dev/workflow.yaml as 'workflow-config' parameter to the workflow.xml instructions +4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions +5. Save outputs after EACH section when generating any documents from templates + diff --git a/.cursor/rules/bmad/bmm/workflows/research.mdc b/.cursor/rules/bmad/bmm/workflows/research.mdc new file mode 100644 index 0000000..2bd1bd1 --- /dev/null +++ b/.cursor/rules/bmad/bmm/workflows/research.mdc @@ -0,0 +1,7 @@ +--- +description: BMAD BMM Agent: research +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL @_bmad/bmm/workflows/1-analysis/research/workflow.md, READ its entire contents and follow its directions exactly! diff --git a/.cursor/rules/bmad/bmm/workflows/retrospective.mdc b/.cursor/rules/bmad/bmm/workflows/retrospective.mdc new file mode 100644 index 0000000..e89aa3a --- /dev/null +++ b/.cursor/rules/bmad/bmm/workflows/retrospective.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD BMM Agent: retrospective +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded: + + +1. Always LOAD the FULL @_bmad/core/tasks/workflow.xml +2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @_bmad/bmm/workflows/4-implementation/retrospective/workflow.yaml +3. Pass the yaml path _bmad/bmm/workflows/4-implementation/retrospective/workflow.yaml as 'workflow-config' parameter to the workflow.xml instructions +4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions +5. Save outputs after EACH section when generating any documents from templates + diff --git a/.cursor/rules/bmad/bmm/workflows/sprint-planning.mdc b/.cursor/rules/bmad/bmm/workflows/sprint-planning.mdc new file mode 100644 index 0000000..cde00e1 --- /dev/null +++ b/.cursor/rules/bmad/bmm/workflows/sprint-planning.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD BMM Agent: sprint-planning +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded: + + +1. Always LOAD the FULL @_bmad/core/tasks/workflow.xml +2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @_bmad/bmm/workflows/4-implementation/sprint-planning/workflow.yaml +3. Pass the yaml path _bmad/bmm/workflows/4-implementation/sprint-planning/workflow.yaml as 'workflow-config' parameter to the workflow.xml instructions +4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions +5. Save outputs after EACH section when generating any documents from templates + diff --git a/.cursor/rules/bmad/bmm/workflows/sprint-status.mdc b/.cursor/rules/bmad/bmm/workflows/sprint-status.mdc new file mode 100644 index 0000000..79ad83c --- /dev/null +++ b/.cursor/rules/bmad/bmm/workflows/sprint-status.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD BMM Agent: sprint-status +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded: + + +1. Always LOAD the FULL @_bmad/core/tasks/workflow.xml +2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @_bmad/bmm/workflows/4-implementation/sprint-status/workflow.yaml +3. Pass the yaml path _bmad/bmm/workflows/4-implementation/sprint-status/workflow.yaml as 'workflow-config' parameter to the workflow.xml instructions +4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions +5. Save outputs after EACH section when generating any documents from templates + diff --git a/.cursor/rules/bmad/bmm/workflows/testarch-atdd.mdc b/.cursor/rules/bmad/bmm/workflows/testarch-atdd.mdc new file mode 100644 index 0000000..c0a094e --- /dev/null +++ b/.cursor/rules/bmad/bmm/workflows/testarch-atdd.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD BMM Agent: testarch-atdd +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded: + + +1. Always LOAD the FULL @_bmad/core/tasks/workflow.xml +2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @_bmad/bmm/workflows/testarch/atdd/workflow.yaml +3. Pass the yaml path _bmad/bmm/workflows/testarch/atdd/workflow.yaml as 'workflow-config' parameter to the workflow.xml instructions +4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions +5. Save outputs after EACH section when generating any documents from templates + diff --git a/.cursor/rules/bmad/bmm/workflows/testarch-automate.mdc b/.cursor/rules/bmad/bmm/workflows/testarch-automate.mdc new file mode 100644 index 0000000..97a833b --- /dev/null +++ b/.cursor/rules/bmad/bmm/workflows/testarch-automate.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD BMM Agent: testarch-automate +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded: + + +1. Always LOAD the FULL @_bmad/core/tasks/workflow.xml +2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @_bmad/bmm/workflows/testarch/automate/workflow.yaml +3. Pass the yaml path _bmad/bmm/workflows/testarch/automate/workflow.yaml as 'workflow-config' parameter to the workflow.xml instructions +4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions +5. Save outputs after EACH section when generating any documents from templates + diff --git a/.cursor/rules/bmad/bmm/workflows/testarch-ci.mdc b/.cursor/rules/bmad/bmm/workflows/testarch-ci.mdc new file mode 100644 index 0000000..dd8f121 --- /dev/null +++ b/.cursor/rules/bmad/bmm/workflows/testarch-ci.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD BMM Agent: testarch-ci +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded: + + +1. Always LOAD the FULL @_bmad/core/tasks/workflow.xml +2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @_bmad/bmm/workflows/testarch/ci/workflow.yaml +3. Pass the yaml path _bmad/bmm/workflows/testarch/ci/workflow.yaml as 'workflow-config' parameter to the workflow.xml instructions +4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions +5. Save outputs after EACH section when generating any documents from templates + diff --git a/.cursor/rules/bmad/bmm/workflows/testarch-framework.mdc b/.cursor/rules/bmad/bmm/workflows/testarch-framework.mdc new file mode 100644 index 0000000..cb05c50 --- /dev/null +++ b/.cursor/rules/bmad/bmm/workflows/testarch-framework.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD BMM Agent: testarch-framework +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded: + + +1. Always LOAD the FULL @_bmad/core/tasks/workflow.xml +2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @_bmad/bmm/workflows/testarch/framework/workflow.yaml +3. Pass the yaml path _bmad/bmm/workflows/testarch/framework/workflow.yaml as 'workflow-config' parameter to the workflow.xml instructions +4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions +5. Save outputs after EACH section when generating any documents from templates + diff --git a/.cursor/rules/bmad/bmm/workflows/testarch-nfr.mdc b/.cursor/rules/bmad/bmm/workflows/testarch-nfr.mdc new file mode 100644 index 0000000..0271de8 --- /dev/null +++ b/.cursor/rules/bmad/bmm/workflows/testarch-nfr.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD BMM Agent: testarch-nfr +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded: + + +1. Always LOAD the FULL @_bmad/core/tasks/workflow.xml +2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @_bmad/bmm/workflows/testarch/nfr-assess/workflow.yaml +3. Pass the yaml path _bmad/bmm/workflows/testarch/nfr-assess/workflow.yaml as 'workflow-config' parameter to the workflow.xml instructions +4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions +5. Save outputs after EACH section when generating any documents from templates + diff --git a/.cursor/rules/bmad/bmm/workflows/testarch-test-design.mdc b/.cursor/rules/bmad/bmm/workflows/testarch-test-design.mdc new file mode 100644 index 0000000..ef5c1d1 --- /dev/null +++ b/.cursor/rules/bmad/bmm/workflows/testarch-test-design.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD BMM Agent: testarch-test-design +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded: + + +1. Always LOAD the FULL @_bmad/core/tasks/workflow.xml +2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @_bmad/bmm/workflows/testarch/test-design/workflow.yaml +3. Pass the yaml path _bmad/bmm/workflows/testarch/test-design/workflow.yaml as 'workflow-config' parameter to the workflow.xml instructions +4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions +5. Save outputs after EACH section when generating any documents from templates + diff --git a/.cursor/rules/bmad/bmm/workflows/testarch-test-review.mdc b/.cursor/rules/bmad/bmm/workflows/testarch-test-review.mdc new file mode 100644 index 0000000..004d334 --- /dev/null +++ b/.cursor/rules/bmad/bmm/workflows/testarch-test-review.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD BMM Agent: testarch-test-review +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded: + + +1. Always LOAD the FULL @_bmad/core/tasks/workflow.xml +2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @_bmad/bmm/workflows/testarch/test-review/workflow.yaml +3. Pass the yaml path _bmad/bmm/workflows/testarch/test-review/workflow.yaml as 'workflow-config' parameter to the workflow.xml instructions +4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions +5. Save outputs after EACH section when generating any documents from templates + diff --git a/.cursor/rules/bmad/bmm/workflows/testarch-trace.mdc b/.cursor/rules/bmad/bmm/workflows/testarch-trace.mdc new file mode 100644 index 0000000..de4db38 --- /dev/null +++ b/.cursor/rules/bmad/bmm/workflows/testarch-trace.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD BMM Agent: testarch-trace +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded: + + +1. Always LOAD the FULL @_bmad/core/tasks/workflow.xml +2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @_bmad/bmm/workflows/testarch/trace/workflow.yaml +3. Pass the yaml path _bmad/bmm/workflows/testarch/trace/workflow.yaml as 'workflow-config' parameter to the workflow.xml instructions +4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions +5. Save outputs after EACH section when generating any documents from templates + diff --git a/.cursor/rules/bmad/bmm/workflows/workflow-init.mdc b/.cursor/rules/bmad/bmm/workflows/workflow-init.mdc new file mode 100644 index 0000000..14eef79 --- /dev/null +++ b/.cursor/rules/bmad/bmm/workflows/workflow-init.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD BMM Agent: workflow-init +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded: + + +1. Always LOAD the FULL @_bmad/core/tasks/workflow.xml +2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @_bmad/bmm/workflows/workflow-status/init/workflow.yaml +3. Pass the yaml path _bmad/bmm/workflows/workflow-status/init/workflow.yaml as 'workflow-config' parameter to the workflow.xml instructions +4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions +5. Save outputs after EACH section when generating any documents from templates + diff --git a/.cursor/rules/bmad/bmm/workflows/workflow-status.mdc b/.cursor/rules/bmad/bmm/workflows/workflow-status.mdc new file mode 100644 index 0000000..238946e --- /dev/null +++ b/.cursor/rules/bmad/bmm/workflows/workflow-status.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD BMM Agent: workflow-status +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THESE STEPS - while staying in character as the current agent persona you may have loaded: + + +1. Always LOAD the FULL @_bmad/core/tasks/workflow.xml +2. READ its entire contents - this is the CORE OS for EXECUTING the specific workflow-config @_bmad/bmm/workflows/workflow-status/workflow.yaml +3. Pass the yaml path _bmad/bmm/workflows/workflow-status/workflow.yaml as 'workflow-config' parameter to the workflow.xml instructions +4. Follow workflow.xml instructions EXACTLY as written to process and follow the specific workflow config and its instructions +5. Save outputs after EACH section when generating any documents from templates + diff --git a/.cursor/rules/bmad/core/agents/bmad-master.mdc b/.cursor/rules/bmad/core/agents/bmad-master.mdc new file mode 100644 index 0000000..81678e4 --- /dev/null +++ b/.cursor/rules/bmad/core/agents/bmad-master.mdc @@ -0,0 +1,15 @@ +--- +description: BMAD CORE Agent: bmad-master +globs: +alwaysApply: false +--- + +You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command. + + +1. LOAD the FULL agent file from @_bmad/core/agents/bmad-master.md +2. READ its entire contents - this contains the complete agent persona, menu, and instructions +3. Execute ALL activation steps exactly as written in the agent file +4. Follow the agent's persona and menu system precisely +5. Stay in character throughout the session + diff --git a/.cursor/rules/bmad/core/tasks/advanced-elicitation.mdc b/.cursor/rules/bmad/core/tasks/advanced-elicitation.mdc new file mode 100644 index 0000000..b145911 --- /dev/null +++ b/.cursor/rules/bmad/core/tasks/advanced-elicitation.mdc @@ -0,0 +1,122 @@ +--- +description: BMAD CORE Task: Advanced Elicitation +globs: +alwaysApply: false +--- + + + + MANDATORY: Execute ALL steps in the flow section IN EXACT ORDER + DO NOT skip steps or change the sequence + HALT immediately when halt-conditions are met + Each action xml tag within step xml tag is a REQUIRED action to complete that step + Sections outside flow (validation, output, critical-context) provide essential context - review and apply throughout execution + + + + When called during template workflow processing: + 1. Receive or review the current section content that was just generated or + 2. Apply elicitation methods iteratively to enhance that specific content + 3. Return the enhanced version back when user selects 'x' to proceed and return back + 4. The enhanced content replaces the original section content in the output document + + + + + Load and read {{methods}} and {{agent-party}} + + + category: Method grouping (core, structural, risk, etc.) + method_name: Display name for the method + description: Rich explanation of what the method does, when to use it, and why it's valuable + output_pattern: Flexible flow guide using → arrows (e.g., "analysis → insights → action") + + + + Use conversation history + Analyze: content type, complexity, stakeholder needs, risk level, and creative potential + + + + 1. Analyze context: Content type, complexity, stakeholder needs, risk level, creative potential + 2. Parse descriptions: Understand each method's purpose from the rich descriptions in CSV + 3. Select 5 methods: Choose methods that best match the context based on their descriptions + 4. Balance approach: Include mix of foundational and specialized techniques as appropriate + + + + + + + **Advanced Elicitation Options (If you launched Party Mode, they will participate randomly)** + Choose a number (1-5), [r] to Reshuffle, [a] List All, or [x] to Proceed: + + 1. [Method Name] + 2. [Method Name] + 3. [Method Name] + 4. [Method Name] + 5. [Method Name] + r. Reshuffle the list with 5 new options + a. List all methods with descriptions + x. Proceed / No Further Actions + + + + + Execute the selected method using its description from the CSV + Adapt the method's complexity and output format based on the current context + Apply the method creatively to the current section content being enhanced + Display the enhanced version showing what the method revealed or improved + CRITICAL: Ask the user if they would like to apply the changes to the doc (y/n/other) and HALT to await response. + CRITICAL: ONLY if Yes, apply the changes. IF No, discard your memory of the proposed changes. If any other reply, try best to + follow the instructions given by the user. + CRITICAL: Re-present the same 1-5,r,x prompt to allow additional elicitations + + + Select 5 random methods from advanced-elicitation-methods.csv, present new list with same prompt format + When selecting, try to think and pick a diverse set of methods covering different categories and approaches, with 1 and 2 being + potentially the most useful for the document or section being discovered + + + Complete elicitation and proceed + Return the fully enhanced content back to create-doc.md + The enhanced content becomes the final version for that section + Signal completion back to create-doc.md to continue with next section + + + List all methods with their descriptions from the CSV in a compact table + Allow user to select any method by name or number from the full list + After selection, execute the method as described in the n="1-5" case above + + + Apply changes to current section content and re-present choices + + + Execute methods in sequence on the content, then re-offer choices + + + + + + Method execution: Use the description from CSV to understand and apply each method + Output pattern: Use the pattern as a flexible guide (e.g., "paths → evaluation → selection") + Dynamic adaptation: Adjust complexity based on content needs (simple to sophisticated) + Creative application: Interpret methods flexibly based on context while maintaining pattern consistency + Focus on actionable insights + Stay relevant: Tie elicitation to specific content being analyzed (the current section from the document being created unless user + indicates otherwise) + Identify personas: For single or multi-persona methods, clearly identify viewpoints, and use party members if available in memory + already + Critical loop behavior: Always re-offer the 1-5,r,a,x choices after each method execution + Continue until user selects 'x' to proceed with enhanced content, confirm or ask the user what should be accepted from the session + Each method application builds upon previous enhancements + Content preservation: Track all enhancements made during elicitation + Iterative enhancement: Each selected method (1-5) should: + 1. Apply to the current enhanced version of the content + 2. Show the improvements made + 3. Return to the prompt for additional elicitations or completion + + + \ No newline at end of file diff --git a/.cursor/rules/bmad/core/tasks/index-docs.mdc b/.cursor/rules/bmad/core/tasks/index-docs.mdc new file mode 100644 index 0000000..71520c9 --- /dev/null +++ b/.cursor/rules/bmad/core/tasks/index-docs.mdc @@ -0,0 +1,71 @@ +--- +description: BMAD CORE Task: Index Docs +globs: +alwaysApply: false +--- + + + + MANDATORY: Execute ALL steps in the flow section IN EXACT ORDER + DO NOT skip steps or change the sequence + HALT immediately when halt-conditions are met + Each action xml tag within step xml tag is a REQUIRED action to complete that step + Sections outside flow (validation, output, critical-context) provide essential context - review and apply throughout execution + + + + + List all files and subdirectories in the target location + + + + Organize files by type, purpose, or subdirectory + + + + Read each file to understand its actual purpose and create brief (3-10 word) descriptions based on the content, not just the + filename + + + + Write or update index.md with organized file listings + + + + + + # Directory Index + + ## Files + + - **[filename.ext](./filename.ext)** - Brief description + - **[another-file.ext](./another-file.ext)** - Brief description + + ## Subdirectories + + ### subfolder/ + + - **[file1.ext](./subfolder/file1.ext)** - Brief description + - **[file2.ext](./subfolder/file2.ext)** - Brief description + + ### another-folder/ + + - **[file3.ext](./another-folder/file3.ext)** - Brief description + + + + + HALT if target directory does not exist or is inaccessible + HALT if user does not have write permissions to create index.md + + + + Use relative paths starting with ./ + Group similar files together + Read file contents to generate accurate descriptions - don't guess from filenames + Keep descriptions concise but informative (3-10 words) + Sort alphabetically within groups + Skip hidden files (starting with .) unless specified + + \ No newline at end of file diff --git a/.cursor/rules/bmad/core/tools/shard-doc.mdc b/.cursor/rules/bmad/core/tools/shard-doc.mdc new file mode 100644 index 0000000..c60fc8f --- /dev/null +++ b/.cursor/rules/bmad/core/tools/shard-doc.mdc @@ -0,0 +1,115 @@ +--- +description: BMAD CORE Tool: Shard Document +globs: +alwaysApply: false +--- + + + Split large markdown documents into smaller, organized files based on level 2 sections using @kayvan/markdown-tree-parser tool + + + MANDATORY: Execute ALL steps in the flow section IN EXACT ORDER + DO NOT skip steps or change the sequence + HALT immediately when halt-conditions are met + Each action xml tag within step xml tag is a REQUIRED action to complete that step + Sections outside flow (validation, output, critical-context) provide essential context - review and apply throughout execution + + + + Uses `npx @kayvan/markdown-tree-parser` to automatically shard documents by level 2 headings and generate an index + + + + + Ask user for the source document path if not provided already + Verify file exists and is accessible + Verify file is markdown format (.md extension) + HALT with error message + + + + Determine default destination: same location as source file, folder named after source file without .md extension + Example: /path/to/architecture.md → /path/to/architecture/ + Ask user for the destination folder path ([y] to confirm use of default: [suggested-path], else enter a new path) + Use the suggested destination path + Use the custom destination path + Verify destination folder exists or can be created + Check write permissions for destination + HALT with error message + + + + Inform user that sharding is beginning + Execute command: `npx @kayvan/markdown-tree-parser explode [source-document] [destination-folder]` + Capture command output and any errors + HALT and display error to user + + + + Check that destination folder contains sharded files + Verify index.md was created in destination folder + Count the number of files created + HALT with error message + + + + Display completion report to user including: + - Source document path and name + - Destination folder path + - Number of section files created + - Confirmation that index.md was created + - Any tool output or warnings + Inform user that sharding completed successfully + + + + Keeping both the original and sharded versions defeats the purpose of sharding and can cause confusion + Present user with options for the original document: + + What would you like to do with the original document `[source-document-name]`? + + Options: + [d] Delete - Remove the original (recommended - shards can always be recombined) + [m] Move to archive - Move original to a backup/archive location + [k] Keep - Leave original in place (NOT recommended - defeats sharding purpose) + + Your choice (d/m/k): + + + Delete the original source document file + Confirm deletion to user: "✓ Original document deleted: [source-document-path]" + The document can be reconstructed from shards by concatenating all section files in order + + + + Determine default archive location: same directory as source, in an "archive" subfolder + Example: /path/to/architecture.md → /path/to/archive/architecture.md + Archive location ([y] to use default: [default-archive-path], or provide custom path): + Use default archive path + Use custom archive path + Create archive directory if it doesn't exist + Move original document to archive location + Confirm move to user: "✓ Original document moved to: [archive-path]" + + + + Display warning to user: + ⚠️ WARNING: Keeping both original and sharded versions is NOT recommended. + + This creates confusion because: + - The discover_inputs protocol may load the wrong version + - Updates to one won't reflect in the other + - You'll have duplicate content taking up space + + Consider deleting or archiving the original document. + Confirm user choice: "Original document kept at: [source-document-path]" + + + + + + HALT if npx command fails or produces no output files + + \ No newline at end of file diff --git a/.cursor/rules/bmad/core/workflows/brainstorming.mdc b/.cursor/rules/bmad/core/workflows/brainstorming.mdc new file mode 100644 index 0000000..467ae63 --- /dev/null +++ b/.cursor/rules/bmad/core/workflows/brainstorming.mdc @@ -0,0 +1,7 @@ +--- +description: BMAD CORE Agent: brainstorming +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL @_bmad/core/workflows/brainstorming/workflow.md, READ its entire contents and follow its directions exactly! diff --git a/.cursor/rules/bmad/core/workflows/party-mode.mdc b/.cursor/rules/bmad/core/workflows/party-mode.mdc new file mode 100644 index 0000000..9edb090 --- /dev/null +++ b/.cursor/rules/bmad/core/workflows/party-mode.mdc @@ -0,0 +1,7 @@ +--- +description: BMAD CORE Agent: party-mode +globs: +alwaysApply: false +--- + +IT IS CRITICAL THAT YOU FOLLOW THIS COMMAND: LOAD the FULL @_bmad/core/workflows/party-mode/workflow.md, READ its entire contents and follow its directions exactly! diff --git a/.cursor/rules/bmad/index.mdc b/.cursor/rules/bmad/index.mdc new file mode 100644 index 0000000..84fe69d --- /dev/null +++ b/.cursor/rules/bmad/index.mdc @@ -0,0 +1,105 @@ +--- +description: BMAD Method - Master Index +globs: +alwaysApply: true +--- + +# BMAD Method - Cursor Rules Index + +This is the master index for all BMAD agents, tasks, tools, and workflows available in your project. + +## Installation Complete! + +BMAD rules have been installed to: `.cursor/rules/bmad/` + +**Note:** BMAD does not modify your `.cursorrules` file. You manage that separately. + +## How to Use + +- Reference specific agents: @bmad/{module}/agents/{agent-name} +- Reference specific tasks: @bmad/{module}/tasks/{task-name} +- Reference specific tools: @bmad/{module}/tools/{tool-name} +- Reference specific workflows: @bmad/{module}/workflows/{workflow-name} +- Reference entire modules: @bmad/{module} +- Reference this index: @bmad/index + +## Available Modules + +### CORE + +**Agents:** +- @bmad/core/agents/bmad-master - bmad-master + +**Tasks:** +- @bmad/core/tasks/advanced-elicitation - advanced-elicitation +- @bmad/core/tasks/index-docs - index-docs + +**Tools:** +- @bmad/core/tools/shard-doc - shard-doc + +**Workflows:** +- @bmad/core/workflows/brainstorming - brainstorming +- @bmad/core/workflows/party-mode - party-mode + +### BMM + +**Agents:** +- @bmad/bmm/agents/analyst - analyst +- @bmad/bmm/agents/architect - architect +- @bmad/bmm/agents/dev - dev +- @bmad/bmm/agents/pm - pm +- @bmad/bmm/agents/quick-flow-solo-dev - quick-flow-solo-dev +- @bmad/bmm/agents/sm - sm +- @bmad/bmm/agents/tea - tea +- @bmad/bmm/agents/tech-writer - tech-writer +- @bmad/bmm/agents/ux-designer - ux-designer + +**Workflows:** +- @bmad/bmm/workflows/create-product-brief - create-product-brief +- @bmad/bmm/workflows/research - research +- @bmad/bmm/workflows/create-ux-design - create-ux-design +- @bmad/bmm/workflows/create-prd - create-prd +- @bmad/bmm/workflows/check-implementation-readiness - check-implementation-readiness +- @bmad/bmm/workflows/create-architecture - create-architecture +- @bmad/bmm/workflows/create-epics-and-stories - create-epics-and-stories +- @bmad/bmm/workflows/code-review - code-review +- @bmad/bmm/workflows/correct-course - correct-course +- @bmad/bmm/workflows/create-story - create-story +- @bmad/bmm/workflows/dev-story - dev-story +- @bmad/bmm/workflows/retrospective - retrospective +- @bmad/bmm/workflows/sprint-planning - sprint-planning +- @bmad/bmm/workflows/sprint-status - sprint-status +- @bmad/bmm/workflows/create-tech-spec - create-tech-spec +- @bmad/bmm/workflows/quick-dev - quick-dev +- @bmad/bmm/workflows/document-project - document-project +- @bmad/bmm/workflows/create-excalidraw-dataflow - create-excalidraw-dataflow +- @bmad/bmm/workflows/create-excalidraw-diagram - create-excalidraw-diagram +- @bmad/bmm/workflows/create-excalidraw-flowchart - create-excalidraw-flowchart +- @bmad/bmm/workflows/create-excalidraw-wireframe - create-excalidraw-wireframe +- @bmad/bmm/workflows/generate-project-context - generate-project-context +- @bmad/bmm/workflows/testarch-atdd - testarch-atdd +- @bmad/bmm/workflows/testarch-automate - testarch-automate +- @bmad/bmm/workflows/testarch-ci - testarch-ci +- @bmad/bmm/workflows/testarch-framework - testarch-framework +- @bmad/bmm/workflows/testarch-nfr - testarch-nfr +- @bmad/bmm/workflows/testarch-test-design - testarch-test-design +- @bmad/bmm/workflows/testarch-test-review - testarch-test-review +- @bmad/bmm/workflows/testarch-trace - testarch-trace +- @bmad/bmm/workflows/workflow-init - workflow-init +- @bmad/bmm/workflows/workflow-status - workflow-status + + +## Quick Reference + +- All BMAD rules are Manual type - reference them explicitly when needed +- Agents provide persona-based assistance with specific expertise +- Tasks are reusable workflows for common operations +- Tools provide specialized functionality +- Workflows orchestrate multi-step processes +- Each agent includes an activation block for proper initialization + +## Configuration + +BMAD rules are configured as Manual rules (alwaysApply: false) to give you control +over when they're included in your context. Reference them explicitly when you need +specific agent expertise, task workflows, tools, or guided workflows. diff --git a/.cursor/rules/orientation.mdc b/.cursor/rules/orientation.mdc new file mode 100644 index 0000000..e43ff20 --- /dev/null +++ b/.cursor/rules/orientation.mdc @@ -0,0 +1,54 @@ +--- +description: Cognitive orientation - project identity and constraints +alwaysApply: true +--- + +# LiquidRender + +DSL-to-React rendering engine built on TurboStarter. + +## What This Is + +A system that compiles a domain-specific language into React UI components. The DSL describes interfaces declaratively; the renderer produces interactive dashboards, forms, and data visualizations. + +## Structure + +``` +packages/liquid-render/ ← Core engine +├── src/compiler/ ← DSL → render tree +├── src/renderer/ ← React components (47 components) +│ └── components/ ← DataTable, Charts, Forms, Layout +└── src/types/ ← Type definitions + +packages/liquid-code/ ← Code generation +packages/liquid-survey/ ← Survey/form builder + +apps/web/ ← Next.js web app +apps/mobile/ ← Expo mobile app +packages/db/ ← Drizzle schemas (10 tables) +``` + +## Core Files + +| What | Where | +|------|-------| +| Main renderer | `liquid-render/src/renderer/LiquidUI.tsx` | +| Design tokens | `liquid-render/src/renderer/components/utils.ts` | +| DSL types | `liquid-render/src/types/` | +| DB schemas | `packages/db/src/schema/` | + +## Before Building Anything + +**Read `capabilities.yaml` first.** Most things already exist. + +## Conventions + +- Design tokens in `utils.ts` - never hardcode colors/spacing +- Components need `data-liquid-type` attribute +- Handle empty/null states in all components + +## Expand + +- Creating components → `cache/answers/how-to-create-component.md` +- Full entity map → `knowledge.json` +- Library docs → `libraries.json` + MCP diff --git a/.cursor/rules/wisdom-chart-patterns.mdc b/.cursor/rules/wisdom-chart-patterns.mdc new file mode 100644 index 0000000..14e7934 --- /dev/null +++ b/.cursor/rules/wisdom-chart-patterns.mdc @@ -0,0 +1,340 @@ +--- +description: --- +--- + +--- +title: "Chart Component Patterns" +purpose: "Creating or modifying chart components in LiquidRender" +answers: + - "How do I use Recharts in LiquidRender?" + - "What color palette should charts use?" + - "How do I configure axes in chart components?" + - "How do I handle SSR for charts?" + - "How do I add legends and tooltips consistently?" + - "How do I transform data for Recharts?" + - "How do I handle empty chart states?" + +read_when: "You're creating, modifying, or debugging chart components" +skip_when: "You're working on non-chart components like tables or forms" + +depends_on: + files: + - "packages/liquid-render/src/renderer/components/utils.ts" + - "packages/liquid-render/src/renderer/components/line-chart.tsx" + - "packages/liquid-render/src/renderer/components/bar-chart.tsx" + - "packages/liquid-render/src/renderer/components/pie-chart.tsx" + - "packages/liquid-render/src/renderer/components/area-chart.tsx" + entities: + - "chartColors" + - "tokens" + - "isBrowser" + - "fieldToLabel" + - "ResponsiveContainer" + concepts: + - "Recharts library" + - "SSR hydration" + - "Design tokens" + +confidence: 0.85 +verified_at: "2025-12-27" +--- + +# Chart Component Patterns + +> **Read when:** You're creating, modifying, or debugging chart components +> +> **Skip when:** You're working on non-chart components like tables or forms + +## Sections + +| Section | Summary | +|---------|---------| +| [Recharts Setup](#recharts-setup) | ResponsiveContainer and SSR handling | +| [Color Palette](#color-palette) | chartColors array from utils.ts | +| [Axis Configuration](#axis-configuration) | XAxis and YAxis consistent styling | +| [Legend & Tooltip](#legend--tooltip) | Tooltip and Legend styling patterns | +| [Data Transformation](#data-transformation) | Auto-detecting x/y fields from data | +| [Empty State Handling](#empty-state-handling) | Placeholder UI for no-data scenarios | + +--- + +## Recharts Setup + +> **TL;DR:** Wrap all charts in `ResponsiveContainer` and check `isBrowser` before rendering Recharts components. + +Charts must be responsive and handle server-side rendering gracefully. The `ResponsiveContainer` makes charts fluid, while `isBrowser` prevents hydration mismatches. + +### Basic Structure + +```tsx +import { + LineChart as RechartsLineChart, + Line, + XAxis, + YAxis, + CartesianGrid, + Tooltip, + Legend, + ResponsiveContainer, +} from 'recharts'; +import { isBrowser } from './utils'; + +// SSR fallback - always check before rendering Recharts +if (!isBrowser) { + return ( +
+ {label &&
{label}
} +
+ [Line chart - {chartData.length} points] +
+
+ ); +} + +// Client-side render with ResponsiveContainer +return ( + + + {/* Chart components */} + + +); +``` + +### Key Points + +- **Always** use `ResponsiveContainer` with `width="100%"` and explicit `height` +- Default chart height is `220px` across all chart types +- Use `margin={{ top: 5, right: 20, bottom: 5, left: 0 }}` for consistent spacing +- The SSR placeholder shows chart type and data summary in brackets + +--- + +## Color Palette + +> **TL;DR:** Import `chartColors` from utils.ts and cycle through with modulo for multi-series charts. + +The color palette is defined in `utils.ts` and provides 8 harmonious colors for data visualization: + +```tsx +import { chartColors } from './utils'; + +// chartColors = [ +// '#3b82f6', // blue +// '#22c55e', // green +// '#f59e0b', // amber +// '#ef4444', // red +// '#8b5cf6', // violet +// '#ec4899', // pink +// '#06b6d4', // cyan +// '#84cc16', // lime +// ] + +// Single series - use first color + + +// Multiple series - cycle with modulo +{numericFields.map((field, i) => ( + +))} +``` + +### Color Usage by Chart Type + +| Chart Type | Color Property | Example | +|------------|----------------|---------| +| Line | `stroke`, `dot.fill` | `stroke={chartColors[0]}` | +| Bar | `fill` | `fill={chartColors[i % chartColors.length]}` | +| Area | `stroke`, `fill` (often gradient) | `stroke={chartColors[0]}` | +| Pie | `Cell` fill | `` | + +--- + +## Axis Configuration + +> **TL;DR:** Use `tokens.colors.mutedForeground` for tick text and `tokens.colors.border` for axis lines. + +All charts follow the same axis styling pattern for visual consistency: + +```tsx +import { tokens } from './utils'; + + + + +``` + +### Axis Style Reference + +| Property | Value | Purpose | +|----------|-------|---------| +| `tick.fontSize` | `12` | Readable but compact labels | +| `tick.fill` | `tokens.colors.mutedForeground` | Subdued text color | +| `stroke` | `tokens.colors.border` | Axis line color | +| `strokeDasharray` | `"3 3"` | CartesianGrid dash pattern | + +--- + +## Legend & Tooltip + +> **TL;DR:** Style tooltips with `tokens.colors.card` background and `tokens.colors.border`; use `fontSize: tokens.fontSize.sm` for legends. + +Consistent tooltip and legend styling across all chart types: + +```tsx +import { tokens } from './utils'; + + + + +``` + +### Tooltip Variations + +```tsx +// Bar chart adds cursor highlight + + +// Pie chart has no cursor (circular) + +``` + +--- + +## Data Transformation + +> **TL;DR:** Use `detectXYFields()` to auto-detect category (string) and value (numeric) fields; use `fieldToLabel()` for display names. + +Charts automatically detect which fields to use based on data types: + +```tsx +import { fieldToLabel } from './utils'; + +// Helper to check numeric values +function isNumeric(value: unknown): value is number { + return typeof value === 'number' && !isNaN(value); +} + +// Auto-detect x (category) and y (numeric) fields +function detectXYFields(data: ChartDataPoint[]): { x: string; y: string } { + if (data.length === 0) return { x: 'x', y: 'y' }; + + const firstRow = data[0]!; + const keys = Object.keys(firstRow); + + // Find first string-like field for x (category/date) + const xField = keys.find(k => { + const val = firstRow[k]; + return typeof val === 'string' || val instanceof Date; + }) || keys[0] || 'x'; + + // Find first numeric field for y + const yField = keys.find(k => { + const val = firstRow[k]; + return isNumeric(val) && k !== xField; + }) || keys[1] || 'y'; + + return { x: xField, y: yField }; +} + +// Detect ALL numeric fields for multi-series charts +function detectAllNumericFields(data: ChartDataPoint[], xField: string): string[] { + if (data.length === 0) return []; + const firstRow = data[0]!; + return Object.keys(firstRow).filter(k => + k !== xField && isNumeric(firstRow[k]) + ); +} + +// Usage: convert field names to labels + "Total Sales" +/> +``` + +### Explicit vs Auto-Detection + +```tsx +// Allow explicit binding override +const { x: xKey, y: yKey } = useMemo(() => { + const explicitX = block.binding?.x; + const explicitY = block.binding?.y; + if (explicitX && explicitY) { + return { x: explicitX, y: explicitY }; + } + return detectXYFields(chartData); // Fallback to auto-detect +}, [block.binding, chartData]); +``` + +--- + +## Empty State Handling + +> **TL;DR:** Return a styled placeholder with "No data available" message when `chartData.length === 0`. + +Every chart must handle empty data gracefully: + +```tsx +// Empty state with consistent styling +if (chartData.length === 0) { + return ( +
+ {label &&
{label}
} +
No data available
+
+ ); +} + +// Placeholder style definition +const styles = { + placeholder: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + height: '220px', + color: tokens.colors.mutedForeground, + fontSize: tokens.fontSize.sm, + textAlign: 'center', + } as React.CSSProperties, +}; +``` + +### State Hierarchy + +1. **SSR fallback** - Check `isBrowser` first, return text placeholder +2. **Empty state** - Check `chartData.length === 0`, return "No data available" +3. **Normal render** - Full chart with data + +--- + +## See Also + +- [utils.ts](/packages/liquid-render/src/renderer/components/utils.ts) - Design tokens and shared utilities +- [COMPONENT-GUIDE.md](/packages/liquid-render/docs/COMPONENT-GUIDE.md) - General component patterns +- [Recharts Documentation](https://recharts.org/en-US/) - Official Recharts API reference diff --git a/.cursor/rules/wisdom-how-to-create-component.mdc b/.cursor/rules/wisdom-how-to-create-component.mdc new file mode 100644 index 0000000..0c035a4 --- /dev/null +++ b/.cursor/rules/wisdom-how-to-create-component.mdc @@ -0,0 +1,386 @@ +--- +description: --- +--- + +--- +title: How to Create a LiquidRender Component +purpose: Creating or modifying LiquidRender components +answers: + - How should I structure a component file? + - How do I use design tokens? + - What's the difference between dynamic and static variants? + - How do I handle empty states? + - How do I format values for display? +read_when: Creating or modifying a component +skip_when: Just using existing components +depends_on: + files: + - packages/liquid-render/src/renderer/components/utils.ts + - packages/liquid-render/docs/COMPONENT-GUIDE.md +--- + +# How to Create a LiquidRender Component + +> **Read when:** Creating or modifying a component + +## Sections + +| Section | Summary | +|---------|---------| +| [File Structure](#file-structure) | Types, Styles, Helpers, Sub-components, Main, Static | +| [Design Tokens](#design-tokens) | Import from utils.ts, never hardcode values | +| [Data Attribute](#data-attribute) | Required `data-liquid-type` on root element | +| [Empty States](#empty-states) | Always handle null/empty data gracefully | +| [Value Formatting](#value-formatting) | Use formatDisplayValue() and fieldToLabel() | +| [Static Variants](#static-variants) | Provide both dynamic and static exports | +| [SSR Handling](#ssr-handling) | Browser detection for chart components | +| [Checklist](#checklist) | Quick validation before submitting | + +--- + +## File Structure + +> **TL;DR:** Follow strict section order: Types, Styles, Helpers, Sub-components, Main, Static. + +Every component file uses section headers for organization: + +```tsx +// [ComponentName] Component - Brief description +import React from 'react'; +import type { LiquidComponentProps } from './utils'; +import { tokens, cardStyles, mergeStyles } from './utils'; +import { resolveBinding } from '../data-context'; + +// ============================================================================ +// Types +// ============================================================================ + +interface ComponentSpecificType { ... } + +// ============================================================================ +// Styles +// ============================================================================ + +const styles = { + wrapper: { ... }, +}; + +// ============================================================================ +// Helpers +// ============================================================================ + +function helperFunction() { ... } + +// ============================================================================ +// Sub-components (if needed) +// ============================================================================ + +function SubComponent() { ... } + +// ============================================================================ +// Main Component +// ============================================================================ + +export function ComponentName({ block, data }: LiquidComponentProps): React.ReactElement { + // 1. Resolve bindings + const value = resolveBinding(block.binding, data); + + // 2. Extract block properties + const label = block.label; + const color = getBlockColor(block); + + // 3. Render + return ( +
+ {/* content */} +
+ ); +} + +// ============================================================================ +// Static Component (standalone usage) +// ============================================================================ + +interface StaticComponentProps { ... } + +export function StaticComponent(props: StaticComponentProps): React.ReactElement { + // For use outside LiquidUI context +} + +export default ComponentName; +``` + +--- + +## Design Tokens + +> **TL;DR:** Import `tokens` from utils.ts. Never hardcode colors, spacing, or sizes. + +```tsx +import { tokens } from './utils'; + +// CORRECT +padding: tokens.spacing.md, +fontSize: tokens.fontSize.sm, +color: tokens.colors.foreground, +borderRadius: tokens.radius.lg, + +// INCORRECT - never hardcode +padding: '16px', +fontSize: '14px', +color: '#0a0a0a', +``` + +### Available Token Categories + +| Category | Examples | Values | +|----------|----------|--------| +| `tokens.colors.*` | `foreground`, `border`, `success` | CSS variables with fallbacks | +| `tokens.spacing.*` | `xs`, `sm`, `md`, `lg`, `xl`, `2xl` | 4px to 48px | +| `tokens.radius.*` | `sm`, `md`, `lg`, `xl`, `full` | 4px to 9999px | +| `tokens.fontSize.*` | `xs`, `sm`, `base`, `lg`, `xl` | 12px to 36px | +| `tokens.fontWeight.*` | `normal`, `medium`, `semibold`, `bold` | 400 to 700 | +| `tokens.shadow.*` | `none`, `sm`, `md`, `lg` | Box shadows | +| `tokens.transition.*` | `fast`, `normal`, `slow` | 150ms to 300ms | + +### Style Helpers + +```tsx +import { cardStyles, buttonStyles, inputStyles, mergeStyles } from './utils'; + +// Card-like containers +const styles = { + wrapper: mergeStyles(cardStyles(), { + padding: tokens.spacing.md, + }), +}; + +// Buttons with variants +const btnStyle = buttonStyles('default', 'md'); // variant, size + +// Input fields +const inputStyle = inputStyles({ /* overrides */ }); +``` + +### Chart Colors + +```tsx +import { chartColors } from './utils'; + +// Consistent palette for data visualization +stroke={chartColors[i % chartColors.length]} +``` + +--- + +## Data Attribute + +> **TL;DR:** Every component MUST have `data-liquid-type` on its root element. + +```tsx +
+
+
+``` + +This enables: +- CSS targeting for styling overrides +- Testing queries for component selection +- Debug inspection in DevTools + +--- + +## Empty States + +> **TL;DR:** Always check for null/empty data and render a graceful fallback. + +```tsx +if (!data || data.length === 0) { + return ( +
+ {label &&
{label}
} +
No data available
+
+ ); +} +``` + +Handle all edge cases: +- `null` or `undefined` data +- Empty arrays +- Missing required fields + +--- + +## Value Formatting + +> **TL;DR:** Use `formatDisplayValue()` for values and `fieldToLabel()` for auto-labels. + +### formatDisplayValue() + +```tsx +import { formatDisplayValue } from './utils'; + +{formatDisplayValue(value)} +``` + +Handles: +- Large numbers: `1234567` becomes `1.2M` +- Thousands: `12345` becomes `12.3K` +- Null/undefined: displays `--` +- Booleans: `Yes`/`No` +- Dates: Localized format + +### fieldToLabel() + +```tsx +import { fieldToLabel } from './utils'; + +// Auto-generate labels from field names +fieldToLabel('totalRevenue') // "Total Revenue" +fieldToLabel('order_count') // "Order Count" +fieldToLabel('avgValue') // "Avg Value" +``` + +### Common Pattern + +```tsx +const label = block.label || fieldToLabel(block.binding?.field || ''); +``` + +--- + +## Static Variants + +> **TL;DR:** Export both `ComponentName` (dynamic) and `StaticComponent` (standalone). + +### Dynamic Component (DSL-driven) + +Used by the LiquidUI renderer. Receives `LiquidComponentProps`: + +```tsx +export function DataTable({ block, data }: LiquidComponentProps) { + const resolvedData = resolveBinding(block.binding, data); + // ... +} +``` + +### Static Component (standalone) + +Used directly in React apps with explicit props: + +```tsx +interface StaticTableProps { + data: Record[]; + columns?: string[]; + title?: string; + sortable?: boolean; +} + +export function StaticTable({ data, columns, title, sortable }: StaticTableProps) { + // No binding resolution needed +} +``` + +### Export Pattern + +```tsx +// In component file +export { ComponentName, StaticComponent }; +export default ComponentName; + +// In index.ts - register for DSL usage +export const liquidComponents = { + typename: ComponentName, +}; +``` + +--- + +## SSR Handling + +> **TL;DR:** Use `isBrowser` check for components requiring browser APIs. + +```tsx +import { isBrowser } from './utils'; + +export function ChartComponent({ block, data }: LiquidComponentProps) { + if (!isBrowser) { + return ( +
+ [Chart placeholder - {data.length} points] +
+ ); + } + + // Browser-only rendering (Recharts, etc.) + return ( + + ... + + ); +} +``` + +--- + +## Checklist + +Before submitting a new component, verify: + +- [ ] File follows standard structure with section headers +- [ ] Uses `tokens` for all style values (no hardcoded colors/spacing) +- [ ] Has `data-liquid-type` attribute on root element +- [ ] Handles empty/null data states +- [ ] Uses `formatDisplayValue()` for value display +- [ ] Uses `fieldToLabel()` for auto-labels +- [ ] Has SSR placeholder if browser-dependent +- [ ] Includes both dynamic and static variants +- [ ] Registered in `liquidComponents` map +- [ ] Exported from `index.ts` + +--- + +## Quick Reference + +### Imports + +```tsx +import type { LiquidComponentProps } from './utils'; +import { + tokens, + chartColors, + cardStyles, + buttonStyles, + inputStyles, + mergeStyles, + getLayoutStyles, + getBlockColor, + formatDisplayValue, + fieldToLabel, + generateId, + isBrowser, +} from './utils'; +import { resolveBinding } from '../data-context'; +``` + +### Props Interface + +```tsx +interface LiquidComponentProps { + block: Block; // Parsed block from DSL + data: DataContext; // Data for binding resolution + children?: ReactNode; + className?: string; +} +``` + +### Block Properties + +```tsx +const value = resolveBinding(block.binding, data); +const label = block.label || fieldToLabel(block.binding?.field || ''); +const color = getBlockColor(block); +const layoutStyles = getLayoutStyles(block); +const columns = block.columns; +``` diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..e09df49 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,37 @@ +# Build outputs +.next +.turbo +dist +out +.expo +.wxt + +# Dependencies (will be installed fresh in Docker) +node_modules +**/node_modules +.pnpm-store + +# Development +.git +.gitignore +*.md +*.log +npm-debug.log + +# IDE +.vscode +.idea + +# Test +coverage +.nyc_output + +# Docker +Dockerfile +.dockerignore + +# Misc +.DS_Store +*.local +.env*.local +tmp/ diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..71a7c4d --- /dev/null +++ b/.env.example @@ -0,0 +1,18 @@ +# Since .env is gitignored, you can use .env.example to build a new `.env` file when you clone the repo. +# Keep this file up-to-date when you add new variables to \`.env\`. + +# This file will be committed to version control, so make sure not to have any secrets in it. +# If you are cloning this repo, create a copy of this file named `.env` and populate it with your secrets. + + +# The database URL is used to connect to your database. +DATABASE_URL="postgresql://turbostarter:turbostarter@localhost:5432/core" + +# The name of the product. This is used in various places across the apps. +PRODUCT_NAME="TurboStarter" + +# The url of the web app. Used mostly to link between apps. +URL="http://localhost:3000" + +# Default locale of the apps, can be overridden separately in each app. +DEFAULT_LOCALE="en" diff --git a/.env.production.example b/.env.production.example new file mode 100644 index 0000000..82c7d5e --- /dev/null +++ b/.env.production.example @@ -0,0 +1,136 @@ +# ============================================================ +# PRODUCTION ENVIRONMENT VARIABLES +# ============================================================ +# Copy this file and fill in your values: +# cp .env.production.example .env.production +# +# Legend: +# [REQUIRED] — App will NOT start without this +# [FEATURE] — Required only when the feature is enabled +# [OPTIONAL] — Has a sensible default, override if needed +# ============================================================ + +# ── CORE (REQUIRED) ────────────────────────────────────────── + +# PostgreSQL connection string +# [REQUIRED] App crashes immediately without this +DATABASE_URL="postgresql://user:password@host:5432/dbname" + +# Secret for signing auth sessions/tokens +# [REQUIRED] Generate with: openssl rand -base64 32 +BETTER_AUTH_SECRET="" + +# Public URL of the app (used for OAuth callbacks, email links, OG meta) +# [REQUIRED] Set at BUILD TIME via --build-arg NEXT_PUBLIC_URL=... +NEXT_PUBLIC_URL="https://your-app.example.com" + +# Comma-separated list of allowed origins for CSRF protection +# [REQUIRED] Must include your public URL +BETTER_AUTH_TRUSTED_ORIGINS="https://your-app.example.com" + +# ── PRODUCT ────────────────────────────────────────────────── + +# [OPTIONAL] App display name (default: "TurboStarter") +NEXT_PUBLIC_PRODUCT_NAME="MyApp" + +# [OPTIONAL] Contact email shown in the app +CONTACT_EMAIL="hello@example.com" + +# [OPTIONAL] Default locale (default: "en") +NEXT_PUBLIC_DEFAULT_LOCALE="en" + +# [OPTIONAL] Theme mode: "light" | "dark" | "system" (default: "system") +NEXT_PUBLIC_THEME_MODE="system" + +# [OPTIONAL] Theme color (default: "orange") +NEXT_PUBLIC_THEME_COLOR="orange" + +# ── AUTH METHODS ───────────────────────────────────────────── + +# [OPTIONAL] Toggle auth methods (default shown) +NEXT_PUBLIC_AUTH_PASSWORD=true +NEXT_PUBLIC_AUTH_MAGIC_LINK=false +NEXT_PUBLIC_AUTH_PASSKEY=true +NEXT_PUBLIC_AUTH_ANONYMOUS=true + +# [OPTIONAL] Signup credits (default: 100 in production) +FREE_TIER_CREDITS=100 + +# Seed user for initial setup +SEED_EMAIL="admin@example.com" +SEED_PASSWORD="ChangeThisPassword!" + +# ── OAUTH PROVIDERS ────────────────────────────────────────── +# Leave empty to disable the provider (login button won't show) + +# [FEATURE] Google OAuth +GOOGLE_CLIENT_ID="" +GOOGLE_CLIENT_SECRET="" + +# [FEATURE] GitHub OAuth +GITHUB_CLIENT_ID="" +GITHUB_CLIENT_SECRET="" + +# [FEATURE] Apple OAuth +APPLE_CLIENT_ID="" +APPLE_CLIENT_SECRET="" +APPLE_APP_BUNDLE_IDENTIFIER="" + +# ── S3 STORAGE ─────────────────────────────────────────────── +# Set S3_BUCKET to enable. All other S3 vars become required. + +# [FEATURE] S3-compatible storage (MinIO, AWS S3, R2, etc.) +S3_BUCKET="" +S3_REGION="us-east-1" +S3_ENDPOINT="http://minio:9000" +S3_ACCESS_KEY_ID="" +S3_SECRET_ACCESS_KEY="" + +# ── BILLING (STRIPE) ──────────────────────────────────────── +# Set STRIPE_SECRET_KEY to enable. Webhook secret becomes required. + +# [FEATURE] Stripe payments +STRIPE_SECRET_KEY="" +STRIPE_WEBHOOK_SECRET="" + +# [OPTIONAL] Billing model: "recurring" | "one_time" | "credits" +BILLING_MODEL="recurring" + +# ── EMAIL ──────────────────────────────────────────────────── +# Configure ONE email provider. Set the API key to enable. + +# [FEATURE] Resend (recommended) +RESEND_API_KEY="" + +# [FEATURE] OR Nodemailer (self-hosted SMTP) +# NODEMAILER_HOST="" +# NODEMAILER_PORT="465" +# NODEMAILER_USER="" +# NODEMAILER_PASSWORD="" + +# [FEATURE] Sender address (required when any email provider is configured) +EMAIL_FROM="noreply@yourdomain.com" + +# ── MONITORING ─────────────────────────────────────────────── +# All monitoring is optional. App works fine without it. + +# [OPTIONAL] Sentry error tracking +NEXT_PUBLIC_SENTRY_DSN="" + +# [OPTIONAL] PostHog analytics +NEXT_PUBLIC_POSTHOG_KEY="" +NEXT_PUBLIC_POSTHOG_HOST="https://us.i.posthog.com" + +# ── AI (if using AI features) ─────────────────────────────── + +# [FEATURE] OpenAI +# OPENAI_API_KEY="" + +# [FEATURE] Anthropic +# ANTHROPIC_API_KEY="" + +# [FEATURE] ElevenLabs TTS +# ELEVENLABS_API_KEY="" + +# [FEATURE] Tavily search +# TAVILY_API_KEY="" diff --git a/.github/DISCUSSION_TEMPLATE/ideas.yml b/.github/DISCUSSION_TEMPLATE/ideas.yml new file mode 100644 index 0000000..c15eba2 --- /dev/null +++ b/.github/DISCUSSION_TEMPLATE/ideas.yml @@ -0,0 +1,21 @@ +body: + - type: markdown + attributes: + value: | + Thank you for taking the time to file a feature request. Please fill out this form as completely as possible. + - type: textarea + attributes: + label: Describe the feature you'd like to request + description: Please describe the feature as clear and concise as possible. Remember to add context as to why you believe this feature is needed. + validations: + required: true + - type: textarea + attributes: + label: Describe the solution you'd like to see + description: Please describe the solution you would like to see. Adding example usage is a good way to provide context. + validations: + required: true + - type: textarea + attributes: + label: Additional information + description: Add any other information related to the feature here. If your feature request is related to any issues or discussions, link them here. diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml new file mode 100644 index 0000000..40709d2 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -0,0 +1,53 @@ +name: "Bug report" +description: Report an issue +title: "[Bug]: " +labels: ["bug"] +body: + - type: markdown + attributes: + value: | + ### Thanks for taking the time to create a bug report. Please search open/closed issues before submitting, as the issue may have already been reported/addressed. + + - type: markdown + attributes: + value: | + #### If you aren't sure this is a bug or not, please open a discussion instead: + - [Discussions](https://github.com/turbostarter/core/discussions/new?category=general) + + - type: textarea + id: bug-description + attributes: + label: Describe the bug + description: A clear and concise description of what the bug is. If you intend to submit a PR for this issue, tell us how in the description. Thanks! + placeholder: Bug description + validations: + required: true + + - type: textarea + id: reproduction + attributes: + label: How to reproduce + description: A step-by-step description of how to reproduce the bug. + placeholder: | + 1. Go to '...' + 2. Click on '....' + 3. See error + validations: + required: true + + - type: textarea + id: logs + attributes: + label: Logs + description: "Please include browser console and server logs around the time this bug occurred. Optional if provided reproduction. Please try not to insert an image but copy paste the log text." + render: bash + + - type: textarea + id: system-info + attributes: + label: System Info + description: Information about browsers, system or binaries that's relevant. + render: bash + placeholder: System, Binaries, Browsers + validations: + required: true diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000..785a775 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,4 @@ +contact_links: + - name: Get Help + url: https://github.com/turbostarter/core/discussions/new?category=general + about: If you can't get something to work the way you expect, open a question in our discussion forums. diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml new file mode 100644 index 0000000..a13c72f --- /dev/null +++ b/.github/ISSUE_TEMPLATE/feature_request.yml @@ -0,0 +1,35 @@ +name: "Feature request" +description: Suggest an idea for this project +title: "[Feature]: " +labels: ["request"] +body: + - type: markdown + attributes: + value: | + ### Thanks for taking the time to create a feature request! Please search open/closed issues before submitting, as the issue may have already been reported/addressed. + + - type: markdown + attributes: + value: | + #### If you aren't sure this is a bug or not, please open a discussion instead: + - [Discussions](https://github.com/turbostarter/core/discussions/new?category=general) + + - type: textarea + id: feature-description + attributes: + label: Feature description + description: Tell us about your feature request + placeholder: "I think this feature would be great because..." + value: "Describe your feature request..." + validations: + required: true + + - type: textarea + id: context + attributes: + label: Additional Context + description: Add any other context about the feature here. + placeholder: ex. screenshots, Stack Overflow links, forum links, etc. + value: "Additional details here..." + validations: + required: false diff --git a/.github/renovate.json b/.github/renovate.json new file mode 100644 index 0000000..11457b1 --- /dev/null +++ b/.github/renovate.json @@ -0,0 +1,23 @@ +{ + "enabled": true, + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": ["config:recommended"], + "packageRules": [ + { + "enabled": false, + "matchPackageNames": ["/^@turbostarter//"] + } + ], + "updateInternalDeps": true, + "rangeStrategy": "bump", + "automerge": true, + "prConcurrentLimit": 5, + "prHourlyLimit": 2, + "schedule": ["on the first day of the month"], + "npm": { + "managerFilePatterns": [ + "/(^|/)package\\.json$/", + "/(^|/)package\\.json\\.hbs$/" + ] + } +} diff --git a/.github/workflows/ai-review.yml b/.github/workflows/ai-review.yml new file mode 100644 index 0000000..0e03488 --- /dev/null +++ b/.github/workflows/ai-review.yml @@ -0,0 +1,47 @@ +name: CI / AI Review + +on: + pull_request: + types: [opened, reopened, ready_for_review, synchronize] + issue_comment: +jobs: + gpt-review: + if: ${{ github.event.sender.type != 'Bot' }} + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + issues: write + pull-requests: write + contents: write + steps: + - name: 🔍 Review + id: ai-review + uses: qodo-ai/pr-agent@main + env: + OPENAI_KEY: ${{ secrets.OPENAI_KEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + github_action_config.auto_describe: "false" + + claude-review: + if: ${{ github.event.sender.type != 'Bot' }} + runs-on: ubuntu-latest + timeout-minutes: 5 + permissions: + contents: write + pull-requests: write + issues: write + id-token: write + actions: read # Required for Claude to read CI results on PRs + steps: + - name: Checkout repository + uses: actions/checkout@v5 + with: + fetch-depth: 1 + + - name: Run Claude Code + id: claude + uses: anthropics/claude-code-action@beta + with: + anthropic_api_key: ${{ secrets.ANTHROPIC_KEY }} + additional_permissions: | + actions: read diff --git a/.github/workflows/cognitive-context.yaml b/.github/workflows/cognitive-context.yaml new file mode 100644 index 0000000..fba7c9d --- /dev/null +++ b/.github/workflows/cognitive-context.yaml @@ -0,0 +1,72 @@ +name: Cognitive Context + +on: + push: + branches: [main, master] + paths: + - "packages/cognitive-context/**" + - ".cognitive/**" + - ".cursor/**" + - "CLAUDE.md" + pull_request: + paths: + - "packages/cognitive-context/**" + - ".cognitive/**" + - ".cursor/**" + - "CLAUDE.md" + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + FORCE_COLOR: "1" + NODE_VERSION: 22.x + TURBO_TEAM: ${{ secrets.TURBO_TEAM }} + TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} + +jobs: + validate: + name: 🧠 Validate Context + runs-on: ubuntu-latest + steps: + - name: ✅ Checkout code + uses: actions/checkout@v5 + + - name: 🔨 Setup + uses: ./tooling/github/setup + with: + node-version: ${{ env.NODE_VERSION }} + + - name: 🏗️ Build cognitive-context + run: pnpm --filter @repo/cognitive-context build + + - name: 🔍 Validate cognitive context + run: pnpm --filter @repo/cognitive-context exec cognitive validate + continue-on-error: false + + sync-check: + name: 🔄 Sync Check + runs-on: ubuntu-latest + steps: + - name: ✅ Checkout code + uses: actions/checkout@v5 + + - name: 🔨 Setup + uses: ./tooling/github/setup + with: + node-version: ${{ env.NODE_VERSION }} + + - name: 🏗️ Build cognitive-context + run: pnpm --filter @repo/cognitive-context build + + - name: 🔍 Check sync status (dry-run) + run: | + OUTPUT=$(pnpm --filter @repo/cognitive-context exec cognitive sync --dry-run 2>&1) || true + echo "$OUTPUT" + if echo "$OUTPUT" | grep -q "would be updated\|would be created\|out of sync"; then + echo "::error::Cognitive context is out of sync. Run 'cognitive sync' locally and commit changes." + exit 1 + fi + echo "✅ All tool configurations are in sync" diff --git a/.github/workflows/publish-db.yml b/.github/workflows/publish-db.yml new file mode 100644 index 0000000..546d16c --- /dev/null +++ b/.github/workflows/publish-db.yml @@ -0,0 +1,30 @@ +name: CI / Publish db + +on: + workflow_dispatch: + +env: + NODE_VERSION: 22.x + +jobs: + db: + name: 🚀 Publish database + runs-on: ubuntu-latest + environment: Production + env: + DATABASE_URL: ${{ secrets.DATABASE_URL }} + + steps: + - name: ✅ Checkout code + uses: actions/checkout@v5 + + - name: 🔨 Setup + uses: ./tooling/github/setup + with: + node-version: ${{ env.NODE_VERSION }} + + - name: 🔍 Check database + run: pnpm --filter @turbostarter/db db:check + + - name: 💨 Migrate! + run: pnpm --filter @turbostarter/db db:migrate diff --git a/.github/workflows/publish-mobile.yml b/.github/workflows/publish-mobile.yml new file mode 100644 index 0000000..8f57a7d --- /dev/null +++ b/.github/workflows/publish-mobile.yml @@ -0,0 +1,36 @@ +name: CI / Publish mobile + +on: + workflow_dispatch: + +env: + NODE_VERSION: 22.x + +jobs: + tests: + name: 🧪 Tests + secrets: inherit + uses: ./.github/workflows/tests.yml + + publish: + name: 🚀 Publish mobile + runs-on: ubuntu-latest + environment: Production + needs: [tests] + steps: + - name: ✅ Checkout code + uses: actions/checkout@v5 + + - name: 🔨 Setup + uses: ./tooling/github/setup + with: + node-version: ${{ env.NODE_VERSION }} + + - name: 🏗 Setup EAS + uses: expo/expo-github-action@v8 + with: + eas-version: latest + token: ${{ secrets.EXPO_TOKEN }} + + - name: 💨 Publish! + run: cd apps/mobile && eas build --platform all --profile production --non-interactive --no-wait --auto-submit diff --git a/.github/workflows/publish-web.yml b/.github/workflows/publish-web.yml new file mode 100644 index 0000000..dab8de8 --- /dev/null +++ b/.github/workflows/publish-web.yml @@ -0,0 +1,43 @@ +name: CI / Publish web + +on: + workflow_dispatch: + +env: + NODE_VERSION: 22.x + +jobs: + tests: + name: 🧪 Tests + secrets: inherit + uses: ./.github/workflows/tests.yml + + publish: + name: 🚀 Publish web + runs-on: ubuntu-latest + environment: Production + needs: [tests] + env: + VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }} + VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }} + + steps: + - name: ✅ Checkout code + uses: actions/checkout@v5 + + - name: 🔨 Setup + uses: ./tooling/github/setup + with: + node-version: ${{ env.NODE_VERSION }} + + - name: 🔼 Install Vercel CLI + run: pnpm install --global vercel@latest + + - name: 💪 Pull environment information + run: vercel pull --yes --environment=production --token=${{ secrets.VERCEL_TOKEN }} + + - name: 📦 Build + run: vercel build --prod --token=${{ secrets.VERCEL_TOKEN }} + + - name: 💨 Publish! + run: vercel deploy --prebuilt --prod --token=${{ secrets.VERCEL_TOKEN }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..6c4e1bb --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,47 @@ +name: CI / Tests + +on: + pull_request: + branches: ["*"] + push: + branches: ["main"] + merge_group: + workflow_dispatch: + workflow_call: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +env: + FORCE_COLOR: "1" + NODE_VERSION: 22.x + # You can leverage Remote Caching with Turbo to speed up your builds + # @link https://turbo.build/repo/docs/guides/ci-vendors/github-actions + TURBO_TEAM: ${{ secrets.TURBO_TEAM }} + TURBO_TOKEN: ${{ secrets.TURBO_TOKEN }} + +jobs: + test: + name: 🧪 Test + runs-on: ubuntu-latest + steps: + - name: ✅ Checkout code + uses: actions/checkout@v5 + + - name: 🔨 Setup + uses: ./tooling/github/setup + with: + node-version: ${{ env.NODE_VERSION }} + + - name: 🖌️ Format + run: pnpm run format + + - name: 🛻 Lint + run: pnpm run lint && pnpm run lint:ws + + - name: 📝 Typecheck + run: pnpm run typecheck + + - name: 🧪 Test + run: pnpm run test diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9635231 --- /dev/null +++ b/.gitignore @@ -0,0 +1,69 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +node_modules +.pnp +.pnp.js + +# testing +coverage +coverage.json + +# next.js +.next/ +out/ +next-env.d.ts + +# nitro +.nitro/ +.output/ + +# expo +.expo/ +expo-env.d.ts +apps/mobile/.gitignore +apps/mobile/ios +apps/mobile/android + +# wxt +.wxt +web-ext.config.ts + +# production +build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* +.pnpm-debug.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +dist/ +.cache + +# turbo +.turbo + +# content collections +.content-collections + +# TTS audio cache +.claude/audio/ + +# Large voice model files +.claude/piper-voices/*.onnx +.mcp.json + +# Auto Claude data directory +.auto-claude/ diff --git a/.husky/commit-msg b/.husky/commit-msg new file mode 100644 index 0000000..66ae5b5 --- /dev/null +++ b/.husky/commit-msg @@ -0,0 +1 @@ +pnpm commitlint --edit $1 \ No newline at end of file diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..07a3149 --- /dev/null +++ b/.npmrc @@ -0,0 +1,3 @@ +node-linker=hoisted +link-workspace-packages=true +save-exact=true \ No newline at end of file diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..e763ca1 --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +22.17 \ No newline at end of file diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000..3606d87 --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,9 @@ +{ + "recommendations": [ + "dbaeumer.vscode-eslint", + "expo.vscode-expo-tools", + "esbenp.prettier-vscode", + "yoavbls.pretty-ts-errors", + "bradlc.vscode-tailwindcss" + ] +} diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..4129c6c --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,70 @@ +{ + "css.customData": [".vscode/tailwind.json"], + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit" + }, + "editor.defaultFormatter": "esbenp.prettier-vscode", + "editor.formatOnSave": true, + "eslint.rules.customizations": [{ "rule": "*", "severity": "warn" }], + "eslint.validate": [ + "javascript", + "javascriptreact", + "typescript", + "typescriptreact" + ], + "eslint.useFlatConfig": true, + "eslint.workingDirectories": [ + { "pattern": "apps/*/" }, + { "pattern": "packages/*/" }, + { "pattern": "packages/{ui,analytics,monitoring}/*" }, + { "pattern": "tooling/*/" } + ], + "prettier.ignorePath": ".gitignore", + "typescript.enablePromptUseWorkspaceTsdk": true, + "typescript.preferences.autoImportFileExcludePatterns": [ + "next/router.d.ts", + "next/dist/client/router.d.ts" + ], + "typescript.tsdk": "node_modules/typescript/lib", + "[typescriptreact]": { + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "files.associations": { + "*.css": "tailwindcss" + }, + "tailwindCSS.experimental.classRegex": [ + ["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"], + ["cx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"] + ], + "tailwindCSS.classAttributes": [ + "class", + "className", + "headerClassName", + "contentContainerClassName", + "columnWrapperClassName", + "endFillColorClassName", + "imageClassName", + "tintColorClassName", + "ios_backgroundColorClassName", + "thumbColorClassName", + "trackColorOnClassName", + "trackColorOffClassName", + "selectionColorClassName", + "cursorColorClassName", + "underlineColorAndroidClassName", + "placeholderTextColorClassName", + "selectionHandleColorClassName", + "colorsClassName", + "progressBackgroundColorClassName", + "titleColorClassName", + "underlayColorClassName", + "colorClassName", + "drawerBackgroundColorClassName", + "statusBarBackgroundColorClassName", + "backdropColorClassName", + "backgroundColorClassName", + "ListFooterComponentClassName", + "ListHeaderComponentClassName" + ], + "tailwindCSS.classFunctions": ["useResolveClassNames"] +} diff --git a/.vscode/tailwind.json b/.vscode/tailwind.json new file mode 100644 index 0000000..a05fbe5 --- /dev/null +++ b/.vscode/tailwind.json @@ -0,0 +1,45 @@ +{ + "version": 1.1, + "atDirectives": [ + { + "name": "@apply", + "description": "Use the `@apply` directive to inline any existing utility classes into your own custom CSS. This is useful when you find a common utility pattern in your HTML that you’d like to extract to a new component.", + "references": [ + { + "name": "Tailwind Documentation", + "url": "https://tailwindcss.com/docs/functions-and-directives#apply" + } + ] + }, + { + "name": "@responsive", + "description": "You can generate responsive variants of your own classes by wrapping their definitions in the `@responsive` directive:\n```css\n@responsive {\n .alert {\n background-color: #E53E3E;\n }\n}\n```\n", + "references": [ + { + "name": "Tailwind Documentation", + "url": "https://tailwindcss.com/docs/functions-and-directives#responsive" + } + ] + }, + { + "name": "@screen", + "description": "The `@screen` directive allows you to create media queries that reference your breakpoints by **name** instead of duplicating their values in your own CSS:\n```css\n@screen sm {\n /* ... */\n}\n```\n…gets transformed into this:\n```css\n@media (min-width: 640px) {\n /* ... */\n}\n```\n", + "references": [ + { + "name": "Tailwind Documentation", + "url": "https://tailwindcss.com/docs/functions-and-directives#screen" + } + ] + }, + { + "name": "@variants", + "description": "Generate `hover`, `focus`, `active` and other **variants** of your own utilities by wrapping their definitions in the `@variants` directive:\n```css\n@variants hover, focus {\n .btn-brand {\n background-color: #3182CE;\n }\n}\n```\n", + "references": [ + { + "name": "Tailwind Documentation", + "url": "https://tailwindcss.com/docs/functions-and-directives#variants" + } + ] + } + ] +} diff --git a/.windsurf/workflows/bmad/bmm/agents/analyst.md b/.windsurf/workflows/bmad/bmm/agents/analyst.md new file mode 100644 index 0000000..670c81c --- /dev/null +++ b/.windsurf/workflows/bmad/bmm/agents/analyst.md @@ -0,0 +1,14 @@ +--- +description: analyst +auto_execution_mode: 3 +--- + +You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command. + + +1. LOAD the FULL agent file from @_bmad/bmm/agents/analyst.md +2. READ its entire contents - this contains the complete agent persona, menu, and instructions +3. Execute ALL activation steps exactly as written in the agent file +4. Follow the agent's persona and menu system precisely +5. Stay in character throughout the session + diff --git a/.windsurf/workflows/bmad/bmm/agents/architect.md b/.windsurf/workflows/bmad/bmm/agents/architect.md new file mode 100644 index 0000000..a26a775 --- /dev/null +++ b/.windsurf/workflows/bmad/bmm/agents/architect.md @@ -0,0 +1,14 @@ +--- +description: architect +auto_execution_mode: 3 +--- + +You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command. + + +1. LOAD the FULL agent file from @_bmad/bmm/agents/architect.md +2. READ its entire contents - this contains the complete agent persona, menu, and instructions +3. Execute ALL activation steps exactly as written in the agent file +4. Follow the agent's persona and menu system precisely +5. Stay in character throughout the session + diff --git a/.windsurf/workflows/bmad/bmm/agents/dev.md b/.windsurf/workflows/bmad/bmm/agents/dev.md new file mode 100644 index 0000000..93f6881 --- /dev/null +++ b/.windsurf/workflows/bmad/bmm/agents/dev.md @@ -0,0 +1,14 @@ +--- +description: dev +auto_execution_mode: 3 +--- + +You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command. + + +1. LOAD the FULL agent file from @_bmad/bmm/agents/dev.md +2. READ its entire contents - this contains the complete agent persona, menu, and instructions +3. Execute ALL activation steps exactly as written in the agent file +4. Follow the agent's persona and menu system precisely +5. Stay in character throughout the session + diff --git a/.windsurf/workflows/bmad/bmm/agents/pm.md b/.windsurf/workflows/bmad/bmm/agents/pm.md new file mode 100644 index 0000000..547d13e --- /dev/null +++ b/.windsurf/workflows/bmad/bmm/agents/pm.md @@ -0,0 +1,14 @@ +--- +description: pm +auto_execution_mode: 3 +--- + +You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command. + + +1. LOAD the FULL agent file from @_bmad/bmm/agents/pm.md +2. READ its entire contents - this contains the complete agent persona, menu, and instructions +3. Execute ALL activation steps exactly as written in the agent file +4. Follow the agent's persona and menu system precisely +5. Stay in character throughout the session + diff --git a/.windsurf/workflows/bmad/bmm/agents/quick-flow-solo-dev.md b/.windsurf/workflows/bmad/bmm/agents/quick-flow-solo-dev.md new file mode 100644 index 0000000..c75df39 --- /dev/null +++ b/.windsurf/workflows/bmad/bmm/agents/quick-flow-solo-dev.md @@ -0,0 +1,14 @@ +--- +description: quick-flow-solo-dev +auto_execution_mode: 3 +--- + +You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command. + + +1. LOAD the FULL agent file from @_bmad/bmm/agents/quick-flow-solo-dev.md +2. READ its entire contents - this contains the complete agent persona, menu, and instructions +3. Execute ALL activation steps exactly as written in the agent file +4. Follow the agent's persona and menu system precisely +5. Stay in character throughout the session + diff --git a/.windsurf/workflows/bmad/bmm/agents/sm.md b/.windsurf/workflows/bmad/bmm/agents/sm.md new file mode 100644 index 0000000..d55e507 --- /dev/null +++ b/.windsurf/workflows/bmad/bmm/agents/sm.md @@ -0,0 +1,14 @@ +--- +description: sm +auto_execution_mode: 3 +--- + +You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command. + + +1. LOAD the FULL agent file from @_bmad/bmm/agents/sm.md +2. READ its entire contents - this contains the complete agent persona, menu, and instructions +3. Execute ALL activation steps exactly as written in the agent file +4. Follow the agent's persona and menu system precisely +5. Stay in character throughout the session + diff --git a/.windsurf/workflows/bmad/bmm/agents/tea.md b/.windsurf/workflows/bmad/bmm/agents/tea.md new file mode 100644 index 0000000..26d4fb9 --- /dev/null +++ b/.windsurf/workflows/bmad/bmm/agents/tea.md @@ -0,0 +1,14 @@ +--- +description: tea +auto_execution_mode: 3 +--- + +You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command. + + +1. LOAD the FULL agent file from @_bmad/bmm/agents/tea.md +2. READ its entire contents - this contains the complete agent persona, menu, and instructions +3. Execute ALL activation steps exactly as written in the agent file +4. Follow the agent's persona and menu system precisely +5. Stay in character throughout the session + diff --git a/.windsurf/workflows/bmad/bmm/agents/tech-writer.md b/.windsurf/workflows/bmad/bmm/agents/tech-writer.md new file mode 100644 index 0000000..7732464 --- /dev/null +++ b/.windsurf/workflows/bmad/bmm/agents/tech-writer.md @@ -0,0 +1,14 @@ +--- +description: tech-writer +auto_execution_mode: 3 +--- + +You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command. + + +1. LOAD the FULL agent file from @_bmad/bmm/agents/tech-writer.md +2. READ its entire contents - this contains the complete agent persona, menu, and instructions +3. Execute ALL activation steps exactly as written in the agent file +4. Follow the agent's persona and menu system precisely +5. Stay in character throughout the session + diff --git a/.windsurf/workflows/bmad/bmm/agents/ux-designer.md b/.windsurf/workflows/bmad/bmm/agents/ux-designer.md new file mode 100644 index 0000000..4c1dd76 --- /dev/null +++ b/.windsurf/workflows/bmad/bmm/agents/ux-designer.md @@ -0,0 +1,14 @@ +--- +description: ux-designer +auto_execution_mode: 3 +--- + +You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command. + + +1. LOAD the FULL agent file from @_bmad/bmm/agents/ux-designer.md +2. READ its entire contents - this contains the complete agent persona, menu, and instructions +3. Execute ALL activation steps exactly as written in the agent file +4. Follow the agent's persona and menu system precisely +5. Stay in character throughout the session + diff --git a/.windsurf/workflows/bmad/bmm/workflows/code-review.md b/.windsurf/workflows/bmad/bmm/workflows/code-review.md new file mode 100644 index 0000000..8afdfaf --- /dev/null +++ b/.windsurf/workflows/bmad/bmm/workflows/code-review.md @@ -0,0 +1,58 @@ +--- +description: code-review +auto_execution_mode: 1 +--- + +# Review Story Workflow +name: code-review +description: "Perform an ADVERSARIAL Senior Developer code review that finds 3-10 specific problems in every story. Challenges everything: code quality, test coverage, architecture compliance, security, performance. NEVER accepts `looks good` - must find minimum issues and can auto-fix with user approval." +author: "BMad" + +# Critical variables from config +config_source: "{project-root}/_bmad/bmm/config.yaml" +output_folder: "{config_source}:output_folder" +user_name: "{config_source}:user_name" +communication_language: "{config_source}:communication_language" +user_skill_level: "{config_source}:user_skill_level" +document_output_language: "{config_source}:document_output_language" +date: system-generated +sprint_artifacts: "{config_source}:sprint_artifacts" +sprint_status: "{sprint_artifacts}/sprint-status.yaml || {output_folder}/sprint-status.yaml" + +# Workflow components +installed_path: "{project-root}/_bmad/bmm/workflows/4-implementation/code-review" +instructions: "{installed_path}/instructions.xml" +validation: "{installed_path}/checklist.md" +template: false + +variables: + # Project context + project_context: "**/project-context.md" + story_dir: "{sprint_artifacts}" + +# Smart input file references - handles both whole docs and sharded docs +# Priority: Whole document first, then sharded version +# Strategy: SELECTIVE LOAD - only load the specific epic needed for this story review +input_file_patterns: + architecture: + description: "System architecture for review context" + whole: "{output_folder}/*architecture*.md" + sharded: "{output_folder}/*architecture*/*.md" + load_strategy: "FULL_LOAD" + ux_design: + description: "UX design specification (if UI review)" + whole: "{output_folder}/*ux*.md" + sharded: "{output_folder}/*ux*/*.md" + load_strategy: "FULL_LOAD" + epics: + description: "Epic containing story being reviewed" + whole: "{output_folder}/*epic*.md" + sharded_index: "{output_folder}/*epic*/index.md" + sharded_single: "{output_folder}/*epic*/epic-{{epic_num}}.md" + load_strategy: "SELECTIVE_LOAD" + document_project: + description: "Brownfield project documentation (optional)" + sharded: "{output_folder}/index.md" + load_strategy: "INDEX_GUIDED" + +standalone: true \ No newline at end of file diff --git a/.windsurf/workflows/bmad/bmm/workflows/correct-course.md b/.windsurf/workflows/bmad/bmm/workflows/correct-course.md new file mode 100644 index 0000000..654cd7d --- /dev/null +++ b/.windsurf/workflows/bmad/bmm/workflows/correct-course.md @@ -0,0 +1,61 @@ +--- +description: correct-course +auto_execution_mode: 1 +--- + +# Correct Course - Sprint Change Management Workflow +name: "correct-course" +description: "Navigate significant changes during sprint execution by analyzing impact, proposing solutions, and routing for implementation" +author: "BMad Method" + +config_source: "{project-root}/_bmad/bmm/config.yaml" +output_folder: "{config_source}:output_folder" +user_name: "{config_source}:user_name" +communication_language: "{config_source}:communication_language" +user_skill_level: "{config_source}:user_skill_level" +document_output_language: "{config_source}:document_output_language" +date: system-generated +sprint_artifacts: "{config_source}:sprint_artifacts" +sprint_status: "{sprint_artifacts}/sprint-status.yaml || {output_folder}/sprint-status.yaml" + +# Smart input file references - handles both whole docs and sharded docs +# Priority: Whole document first, then sharded version +# Strategy: Load project context for impact analysis +input_file_patterns: + prd: + description: "Product requirements for impact analysis" + whole: "{output_folder}/*prd*.md" + sharded: "{output_folder}/*prd*/*.md" + load_strategy: "FULL_LOAD" + epics: + description: "All epics to analyze change impact" + whole: "{output_folder}/*epic*.md" + sharded: "{output_folder}/*epic*/*.md" + load_strategy: "FULL_LOAD" + architecture: + description: "System architecture and decisions" + whole: "{output_folder}/*architecture*.md" + sharded: "{output_folder}/*architecture*/*.md" + load_strategy: "FULL_LOAD" + ux_design: + description: "UX design specification (if UI impacts)" + whole: "{output_folder}/*ux*.md" + sharded: "{output_folder}/*ux*/*.md" + load_strategy: "FULL_LOAD" + tech_spec: + description: "Technical specification" + whole: "{output_folder}/tech-spec*.md" + load_strategy: "FULL_LOAD" + document_project: + description: "Brownfield project documentation (optional)" + sharded: "{output_folder}/index.md" + load_strategy: "INDEX_GUIDED" + +installed_path: "{project-root}/_bmad/bmm/workflows/4-implementation/correct-course" +template: false +instructions: "{installed_path}/instructions.md" +validation: "{installed_path}/checklist.md" +checklist: "{installed_path}/checklist.md" +default_output_file: "{output_folder}/sprint-change-proposal-{date}.md" + +standalone: true diff --git a/.windsurf/workflows/bmad/bmm/workflows/create-excalidraw-dataflow.md b/.windsurf/workflows/bmad/bmm/workflows/create-excalidraw-dataflow.md new file mode 100644 index 0000000..02d18ce --- /dev/null +++ b/.windsurf/workflows/bmad/bmm/workflows/create-excalidraw-dataflow.md @@ -0,0 +1,31 @@ +--- +description: create-excalidraw-dataflow +auto_execution_mode: 1 +--- + +name: create-excalidraw-dataflow +description: "Create data flow diagrams (DFD) in Excalidraw format" +author: "BMad" + +# Config values +config_source: "{project-root}/_bmad/bmm/config.yaml" +output_folder: "{config_source}:output_folder" + +# Workflow components +installed_path: "{project-root}/_bmad/bmm/workflows/excalidraw-diagrams/create-dataflow" +shared_path: "{project-root}/_bmad/bmm/workflows/excalidraw-diagrams/_shared" +instructions: "{installed_path}/instructions.md" +validation: "{installed_path}/checklist.md" + +# Core Excalidraw resources (universal knowledge) +helpers: "{project-root}/_bmad/core/resources/excalidraw/excalidraw-helpers.md" +json_validation: "{project-root}/_bmad/core/resources/excalidraw/validate-json-instructions.md" + +# Domain-specific resources (technical diagrams) +templates: "{shared_path}/excalidraw-templates.yaml" +library: "{shared_path}/excalidraw-library.json" + +# Output file (respects user's configured output_folder) +default_output_file: "{output_folder}/excalidraw-diagrams/dataflow-{timestamp}.excalidraw" + +standalone: true \ No newline at end of file diff --git a/.windsurf/workflows/bmad/bmm/workflows/create-excalidraw-diagram.md b/.windsurf/workflows/bmad/bmm/workflows/create-excalidraw-diagram.md new file mode 100644 index 0000000..593876a --- /dev/null +++ b/.windsurf/workflows/bmad/bmm/workflows/create-excalidraw-diagram.md @@ -0,0 +1,31 @@ +--- +description: create-excalidraw-diagram +auto_execution_mode: 1 +--- + +name: create-excalidraw-diagram +description: "Create system architecture diagrams, ERDs, UML diagrams, or general technical diagrams in Excalidraw format" +author: "BMad" + +# Config values +config_source: "{project-root}/_bmad/bmm/config.yaml" +output_folder: "{config_source}:output_folder" + +# Workflow components +installed_path: "{project-root}/_bmad/bmm/workflows/excalidraw-diagrams/create-diagram" +shared_path: "{project-root}/_bmad/bmm/workflows/excalidraw-diagrams/_shared" +instructions: "{installed_path}/instructions.md" +validation: "{installed_path}/checklist.md" + +# Core Excalidraw resources (universal knowledge) +helpers: "{project-root}/_bmad/core/resources/excalidraw/excalidraw-helpers.md" +json_validation: "{project-root}/_bmad/core/resources/excalidraw/validate-json-instructions.md" + +# Domain-specific resources (technical diagrams) +templates: "{shared_path}/excalidraw-templates.yaml" +library: "{shared_path}/excalidraw-library.json" + +# Output file (respects user's configured output_folder) +default_output_file: "{output_folder}/excalidraw-diagrams/diagram-{timestamp}.excalidraw" + +standalone: true \ No newline at end of file diff --git a/.windsurf/workflows/bmad/bmm/workflows/create-excalidraw-flowchart.md b/.windsurf/workflows/bmad/bmm/workflows/create-excalidraw-flowchart.md new file mode 100644 index 0000000..bd1362c --- /dev/null +++ b/.windsurf/workflows/bmad/bmm/workflows/create-excalidraw-flowchart.md @@ -0,0 +1,31 @@ +--- +description: create-excalidraw-flowchart +auto_execution_mode: 1 +--- + +name: create-excalidraw-flowchart +description: "Create a flowchart visualization in Excalidraw format for processes, pipelines, or logic flows" +author: "BMad" + +# Config values +config_source: "{project-root}/_bmad/bmm/config.yaml" +output_folder: "{config_source}:output_folder" + +# Workflow components +installed_path: "{project-root}/_bmad/bmm/workflows/excalidraw-diagrams/create-flowchart" +shared_path: "{project-root}/_bmad/bmm/workflows/excalidraw-diagrams/_shared" +instructions: "{installed_path}/instructions.md" +validation: "{installed_path}/checklist.md" + +# Core Excalidraw resources (universal knowledge) +helpers: "{project-root}/_bmad/core/resources/excalidraw/excalidraw-helpers.md" +json_validation: "{project-root}/_bmad/core/resources/excalidraw/validate-json-instructions.md" + +# Domain-specific resources (technical diagrams) +templates: "{shared_path}/excalidraw-templates.yaml" +library: "{shared_path}/excalidraw-library.json" + +# Output file (respects user's configured output_folder) +default_output_file: "{output_folder}/excalidraw-diagrams/flowchart-{timestamp}.excalidraw" + +standalone: true \ No newline at end of file diff --git a/.windsurf/workflows/bmad/bmm/workflows/create-excalidraw-wireframe.md b/.windsurf/workflows/bmad/bmm/workflows/create-excalidraw-wireframe.md new file mode 100644 index 0000000..a67d956 --- /dev/null +++ b/.windsurf/workflows/bmad/bmm/workflows/create-excalidraw-wireframe.md @@ -0,0 +1,31 @@ +--- +description: create-excalidraw-wireframe +auto_execution_mode: 1 +--- + +name: create-excalidraw-wireframe +description: "Create website or app wireframes in Excalidraw format" +author: "BMad" + +# Config values +config_source: "{project-root}/_bmad/bmm/config.yaml" +output_folder: "{config_source}:output_folder" + +# Workflow components +installed_path: "{project-root}/_bmad/bmm/workflows/excalidraw-diagrams/create-wireframe" +shared_path: "{project-root}/_bmad/bmm/workflows/excalidraw-diagrams/_shared" +instructions: "{installed_path}/instructions.md" +validation: "{installed_path}/checklist.md" + +# Core Excalidraw resources (universal knowledge) +helpers: "{project-root}/_bmad/core/resources/excalidraw/excalidraw-helpers.md" +json_validation: "{project-root}/_bmad/core/resources/excalidraw/validate-json-instructions.md" + +# Domain-specific resources (technical diagrams) +templates: "{shared_path}/excalidraw-templates.yaml" +library: "{shared_path}/excalidraw-library.json" + +# Output file (respects user's configured output_folder) +default_output_file: "{output_folder}/excalidraw-diagrams/wireframe-{timestamp}.excalidraw" + +standalone: true \ No newline at end of file diff --git a/.windsurf/workflows/bmad/bmm/workflows/create-story.md b/.windsurf/workflows/bmad/bmm/workflows/create-story.md new file mode 100644 index 0000000..e15a78a --- /dev/null +++ b/.windsurf/workflows/bmad/bmm/workflows/create-story.md @@ -0,0 +1,63 @@ +--- +description: create-story +auto_execution_mode: 1 +--- + +name: create-story +description: "Create the next user story from epics+stories with enhanced context analysis and direct ready-for-dev marking" +author: "BMad" + +# Critical variables from config +config_source: "{project-root}/_bmad/bmm/config.yaml" +output_folder: "{config_source}:output_folder" +user_name: "{config_source}:user_name" +communication_language: "{config_source}:communication_language" +date: system-generated +sprint_artifacts: "{config_source}:sprint_artifacts" +story_dir: "{sprint_artifacts}" + +# Workflow components +installed_path: "{project-root}/_bmad/bmm/workflows/4-implementation/create-story" +template: "{installed_path}/template.md" +instructions: "{installed_path}/instructions.xml" +validation: "{installed_path}/checklist.md" + +# Variables and inputs +variables: + sprint_status: "{sprint_artifacts}/sprint-status.yaml || {output_folder}/sprint-status.yaml" # Primary source for story tracking + epics_file: "{output_folder}/epics.md" # Enhanced epics+stories with BDD and source hints + prd_file: "{output_folder}/PRD.md" # Fallback for requirements (if not in epics file) + architecture_file: "{output_folder}/architecture.md" # Fallback for constraints (if not in epics file) + ux_file: "{output_folder}/ux.md" # Fallback for UX requirements (if not in epics file) + story_title: "" # Will be elicited if not derivable + +# Project context +project_context: "**/project-context.md" + +default_output_file: "{story_dir}/{{story_key}}.md" + +# Smart input file references - Simplified for enhanced approach +# The epics+stories file should contain everything needed with source hints +input_file_patterns: + prd: + description: "PRD (fallback - epics file should have most content)" + whole: "{output_folder}/*prd*.md" + sharded: "{output_folder}/*prd*/*.md" + load_strategy: "SELECTIVE_LOAD" # Only load if needed + architecture: + description: "Architecture (fallback - epics file should have relevant sections)" + whole: "{output_folder}/*architecture*.md" + sharded: "{output_folder}/*architecture*/*.md" + load_strategy: "SELECTIVE_LOAD" # Only load if needed + ux: + description: "UX design (fallback - epics file should have relevant sections)" + whole: "{output_folder}/*ux*.md" + sharded: "{output_folder}/*ux*/*.md" + load_strategy: "SELECTIVE_LOAD" # Only load if needed + epics: + description: "Enhanced epics+stories file with BDD and source hints" + whole: "{output_folder}/*epic*.md" + sharded: "{output_folder}/*epic*/*.md" + load_strategy: "SELECTIVE_LOAD" # Only load needed epic + +standalone: true diff --git a/.windsurf/workflows/bmad/bmm/workflows/create-tech-spec.md b/.windsurf/workflows/bmad/bmm/workflows/create-tech-spec.md new file mode 100644 index 0000000..96b423a --- /dev/null +++ b/.windsurf/workflows/bmad/bmm/workflows/create-tech-spec.md @@ -0,0 +1,30 @@ +--- +description: create-tech-spec +auto_execution_mode: 1 +--- + +# Quick-Flow: Create Tech-Spec +name: create-tech-spec +description: "Conversational spec engineering - ask questions, investigate code, produce implementation-ready tech-spec." +author: "BMad" + +# Config +config_source: "{project-root}/_bmad/bmm/config.yaml" +output_folder: "{config_source}:output_folder" +sprint_artifacts: "{config_source}:sprint_artifacts" +user_name: "{config_source}:user_name" +communication_language: "{config_source}:communication_language" +document_output_language: "{config_source}:document_output_language" +user_skill_level: "{config_source}:user_skill_level" +date: system-generated + +# Workflow components +installed_path: "{project-root}/_bmad/bmm/workflows/bmad-quick-flow/create-tech-spec" +instructions: "{installed_path}/instructions.md" + +# Related workflows +quick_dev_workflow: "{project-root}/_bmad/bmm/workflows/bmad-quick-flow/quick-dev/workflow.yaml" +party_mode_exec: "{project-root}/_bmad/core/workflows/party-mode/workflow.md" +advanced_elicitation: "{project-root}/_bmad/core/tasks/advanced-elicitation.xml" + +standalone: true \ No newline at end of file diff --git a/.windsurf/workflows/bmad/bmm/workflows/dev-story.md b/.windsurf/workflows/bmad/bmm/workflows/dev-story.md new file mode 100644 index 0000000..b5cf687 --- /dev/null +++ b/.windsurf/workflows/bmad/bmm/workflows/dev-story.md @@ -0,0 +1,30 @@ +--- +description: dev-story +auto_execution_mode: 1 +--- + +name: dev-story +description: "Execute a story by implementing tasks/subtasks, writing tests, validating, and updating the story file per acceptance criteria" +author: "BMad" + +# Critical variables from config +config_source: "{project-root}/_bmad/bmm/config.yaml" +output_folder: "{config_source}:output_folder" +user_name: "{config_source}:user_name" +communication_language: "{config_source}:communication_language" +user_skill_level: "{config_source}:user_skill_level" +document_output_language: "{config_source}:document_output_language" +story_dir: "{config_source}:sprint_artifacts" +date: system-generated + +# Workflow components +installed_path: "{project-root}/_bmad/bmm/workflows/4-implementation/dev-story" +instructions: "{installed_path}/instructions.xml" +validation: "{installed_path}/checklist.md" + +story_file: "" # Explicit story path; auto-discovered if empty +sprint_artifacts: "{config_source}:sprint_artifacts" +sprint_status: "{sprint_artifacts}/sprint-status.yaml" +project_context: "**/project-context.md" + +standalone: true diff --git a/.windsurf/workflows/bmad/bmm/workflows/document-project.md b/.windsurf/workflows/bmad/bmm/workflows/document-project.md new file mode 100644 index 0000000..c2e939a --- /dev/null +++ b/.windsurf/workflows/bmad/bmm/workflows/document-project.md @@ -0,0 +1,34 @@ +--- +description: document-project +auto_execution_mode: 1 +--- + +# Document Project Workflow Configuration +name: "document-project" +version: "1.2.0" +description: "Analyzes and documents brownfield projects by scanning codebase, architecture, and patterns to create comprehensive reference documentation for AI-assisted development" +author: "BMad" + +# Critical variables +config_source: "{project-root}/_bmad/bmm/config.yaml" +output_folder: "{config_source}:output_folder" +user_name: "{config_source}:user_name" +communication_language: "{config_source}:communication_language" +document_output_language: "{config_source}:document_output_language" +user_skill_level: "{config_source}:user_skill_level" +date: system-generated + +# Module path and component files +installed_path: "{project-root}/_bmad/bmm/workflows/document-project" +template: false # This is an action workflow with multiple output files +instructions: "{installed_path}/instructions.md" +validation: "{installed_path}/checklist.md" + +# Required data files - CRITICAL for project type detection and documentation requirements +documentation_requirements_csv: "{installed_path}/documentation-requirements.csv" + +# Output configuration - Multiple files generated in output folder +# Primary output: {output_folder}/index.md +# Additional files generated by sub-workflows based on project structure + +standalone: true diff --git a/.windsurf/workflows/bmad/bmm/workflows/quick-dev.md b/.windsurf/workflows/bmad/bmm/workflows/quick-dev.md new file mode 100644 index 0000000..40e8392 --- /dev/null +++ b/.windsurf/workflows/bmad/bmm/workflows/quick-dev.md @@ -0,0 +1,37 @@ +--- +description: quick-dev +auto_execution_mode: 1 +--- + +# Quick-Flow: Quick-Dev +name: quick-dev +description: "Flexible development - execute tech-specs OR direct instructions with optional planning." +author: "BMad" + +# Config +config_source: "{project-root}/_bmad/bmm/config.yaml" +output_folder: "{config_source}:output_folder" +sprint_artifacts: "{config_source}:sprint_artifacts" +user_name: "{config_source}:user_name" +communication_language: "{config_source}:communication_language" +user_skill_level: "{config_source}:user_skill_level" +date: system-generated + +# Project context +project_context: "**/project-context.md" + +# Workflow components +installed_path: "{project-root}/_bmad/bmm/workflows/bmad-quick-flow/quick-dev" +instructions: "{installed_path}/instructions.md" +checklist: "{installed_path}/checklist.md" + +# Related workflows +create_tech_spec_workflow: "{project-root}/_bmad/bmm/workflows/bmad-quick-flow/create-tech-spec/workflow.yaml" +party_mode_exec: "{project-root}/_bmad/core/workflows/party-mode/workflow.md" +advanced_elicitation: "{project-root}/_bmad/core/tasks/advanced-elicitation.xml" + +# Routing resources (lazy-loaded) +project_levels: "{project-root}/_bmad/bmm/workflows/workflow-status/project-levels.yaml" +workflow_init: "{project-root}/_bmad/bmm/workflows/workflow-status/init/workflow.yaml" + +standalone: true \ No newline at end of file diff --git a/.windsurf/workflows/bmad/bmm/workflows/retrospective.md b/.windsurf/workflows/bmad/bmm/workflows/retrospective.md new file mode 100644 index 0000000..6730618 --- /dev/null +++ b/.windsurf/workflows/bmad/bmm/workflows/retrospective.md @@ -0,0 +1,61 @@ +--- +description: retrospective +auto_execution_mode: 1 +--- + +# Retrospective - Epic Completion Review Workflow +name: "retrospective" +description: "Run after epic completion to review overall success, extract lessons learned, and explore if new information emerged that might impact the next epic" +author: "BMad" + +config_source: "{project-root}/_bmad/bmm/config.yaml" +output_folder: "{config_source}:output_folder" +user_name: "{config_source}:user_name" +communication_language: "{config_source}:communication_language" +user_skill_level: "{config_source}:user_skill_level" +document_output_language: "{config_source}:document_output_language" +date: system-generated +sprint_artifacts: "{config_source}:sprint_artifacts" + +installed_path: "{project-root}/_bmad/bmm/workflows/4-implementation/retrospective" +template: false +instructions: "{installed_path}/instructions.md" + +required_inputs: + - agent_manifest: "{project-root}/_bmad/_config/agent-manifest.csv" + +# Smart input file references - handles both whole docs and sharded docs +# Priority: Whole document first, then sharded version +# Strategy: SELECTIVE LOAD - only load the completed epic and relevant retrospectives +input_file_patterns: + epics: + description: "The completed epic for retrospective" + whole: "{output_folder}/*epic*.md" + sharded_index: "{output_folder}/*epic*/index.md" + sharded_single: "{output_folder}/*epic*/epic-{{epic_num}}.md" + load_strategy: "SELECTIVE_LOAD" + previous_retrospective: + description: "Previous epic's retrospective (optional)" + pattern: "{sprint_artifacts}/**/epic-{{prev_epic_num}}-retro-*.md" + load_strategy: "SELECTIVE_LOAD" + architecture: + description: "System architecture for context" + whole: "{output_folder}/*architecture*.md" + sharded: "{output_folder}/*architecture*/*.md" + load_strategy: "FULL_LOAD" + prd: + description: "Product requirements for context" + whole: "{output_folder}/*prd*.md" + sharded: "{output_folder}/*prd*/*.md" + load_strategy: "FULL_LOAD" + document_project: + description: "Brownfield project documentation (optional)" + sharded: "{output_folder}/*.md" + load_strategy: "INDEX_GUIDED" + +# Required files +sprint_status_file: "{sprint_artifacts}/sprint-status.yaml || {output_folder}/sprint-status.yaml" +story_directory: "{sprint_artifacts}" +retrospectives_folder: "{sprint_artifacts}" + +standalone: true \ No newline at end of file diff --git a/.windsurf/workflows/bmad/bmm/workflows/sprint-planning.md b/.windsurf/workflows/bmad/bmm/workflows/sprint-planning.md new file mode 100644 index 0000000..6526ea2 --- /dev/null +++ b/.windsurf/workflows/bmad/bmm/workflows/sprint-planning.md @@ -0,0 +1,56 @@ +--- +description: sprint-planning +auto_execution_mode: 1 +--- + +name: sprint-planning +description: "Generate and manage the sprint status tracking file for Phase 4 implementation, extracting all epics and stories from epic files and tracking their status through the development lifecycle" +author: "BMad" + +# Critical variables from config +config_source: "{project-root}/_bmad/bmm/config.yaml" +output_folder: "{config_source}:output_folder" +user_name: "{config_source}:user_name" +communication_language: "{config_source}:communication_language" +date: system-generated +sprint_artifacts: "{config_source}:sprint_artifacts" + +# Workflow components +installed_path: "{project-root}/_bmad/bmm/workflows/4-implementation/sprint-planning" +instructions: "{installed_path}/instructions.md" +template: "{installed_path}/sprint-status-template.yaml" +validation: "{installed_path}/checklist.md" + +# Variables and inputs +variables: + # Project context + project_context: "**/project-context.md" + # Project identification + project_name: "{config_source}:project_name" + + # Tracking system configuration + tracking_system: "file-system" # Options: file-system, Future will support other options from config of mcp such as jira, linear, trello + story_location: "{config_source}:sprint_artifacts" # Relative path for file-system, Future will support URL for Jira/Linear/Trello + story_location_absolute: "{config_source}:sprint_artifacts" # Absolute path for file operations + + # Source files (file-system only) + epics_location: "{output_folder}" # Directory containing epic*.md files + epics_pattern: "epic*.md" # Pattern to find epic files + + # Output configuration + status_file: "{sprint_artifacts}/sprint-status.yaml" + +# Smart input file references - handles both whole docs and sharded docs +# Priority: Whole document first, then sharded version +# Strategy: FULL LOAD - sprint planning needs ALL epics to build complete status +input_file_patterns: + epics: + description: "All epics with user stories" + whole: "{output_folder}/*epic*.md" + sharded: "{output_folder}/*epic*/*.md" + load_strategy: "FULL_LOAD" + +# Output configuration +default_output_file: "{status_file}" + +standalone: true diff --git a/.windsurf/workflows/bmad/bmm/workflows/sprint-status.md b/.windsurf/workflows/bmad/bmm/workflows/sprint-status.md new file mode 100644 index 0000000..0ce7706 --- /dev/null +++ b/.windsurf/workflows/bmad/bmm/workflows/sprint-status.md @@ -0,0 +1,39 @@ +--- +description: sprint-status +auto_execution_mode: 1 +--- + +# Sprint Status - Implementation Tracker +name: sprint-status +description: "Summarize sprint-status.yaml, surface risks, and route to the right implementation workflow." +author: "BMad" + +# Critical variables from config +config_source: "{project-root}/_bmad/bmm/config.yaml" +output_folder: "{config_source}:output_folder" +user_name: "{config_source}:user_name" +communication_language: "{config_source}:communication_language" +document_output_language: "{config_source}:document_output_language" +date: system-generated +sprint_artifacts: "{config_source}:sprint_artifacts" + +# Workflow components +installed_path: "{project-root}/_bmad/bmm/workflows/4-implementation/sprint-status" +instructions: "{installed_path}/instructions.md" + +# Inputs +variables: + sprint_status_file: "{sprint_artifacts}/sprint-status.yaml || {output_folder}/sprint-status.yaml" + tracking_system: "file-system" + +# Smart input file references +input_file_patterns: + sprint_status: + description: "Sprint status file generated by sprint-planning" + whole: "{sprint_artifacts}/sprint-status.yaml || {output_folder}/sprint-status.yaml" + load_strategy: "FULL_LOAD" + +# Standalone so IDE commands get generated +standalone: true + +# No web bundle needed \ No newline at end of file diff --git a/.windsurf/workflows/bmad/bmm/workflows/workflow-init.md b/.windsurf/workflows/bmad/bmm/workflows/workflow-init.md new file mode 100644 index 0000000..ae5a048 --- /dev/null +++ b/.windsurf/workflows/bmad/bmm/workflows/workflow-init.md @@ -0,0 +1,33 @@ +--- +description: workflow-init +auto_execution_mode: 1 +--- + +# Workflow Init - Initial Project Setup +name: workflow-init +description: "Initialize a new BMM project by determining level, type, and creating workflow path" +author: "BMad" + +# Critical variables from config +config_source: "{project-root}/_bmad/bmm/config.yaml" +output_folder: "{config_source}:output_folder" +sprint_artifacts: "{config_source}:sprint_artifacts" +user_name: "{config_source}:user_name" +project_name: "{config_source}:project_name" +communication_language: "{config_source}:communication_language" +document_output_language: "{config_source}:document_output_language" +user_skill_level: "{config_source}:user_skill_level" +date: system-generated + +# Workflow components +installed_path: "{project-root}/_bmad/bmm/workflows/workflow-status/init" +instructions: "{installed_path}/instructions.md" +template: "{project-root}/_bmad/bmm/workflows/workflow-status/workflow-status-template.yaml" + +# Path data files +path_files: "{project-root}/_bmad/bmm/workflows/workflow-status/paths/" + +# Output configuration +default_output_file: "{output_folder}/bmm-workflow-status.yaml" + +standalone: true \ No newline at end of file diff --git a/.windsurf/workflows/bmad/bmm/workflows/workflow-status.md b/.windsurf/workflows/bmad/bmm/workflows/workflow-status.md new file mode 100644 index 0000000..0adbe74 --- /dev/null +++ b/.windsurf/workflows/bmad/bmm/workflows/workflow-status.md @@ -0,0 +1,33 @@ +--- +description: workflow-status +auto_execution_mode: 1 +--- + +# Workflow Status - Master Router and Status Tracker +name: workflow-status +description: 'Lightweight status checker - answers "what should I do now?" for any agent. Reads YAML status file for workflow tracking. Use workflow-init for new projects.' +author: "BMad" + +# Critical variables from config +config_source: "{project-root}/_bmad/bmm/config.yaml" +output_folder: "{config_source}:output_folder" +user_name: "{config_source}:user_name" +communication_language: "{config_source}:communication_language" +document_output_language: "{config_source}:document_output_language" +user_skill_level: "{config_source}:user_skill_level" +date: system-generated + +# Workflow components +installed_path: "{project-root}/_bmad/bmm/workflows/workflow-status" +instructions: "{installed_path}/instructions.md" + +# Template for status file creation (used by workflow-init) +template: "{installed_path}/workflow-status-template.yaml" + +# Path definitions for project types +path_files: "{installed_path}/paths/" + +# Output configuration - reads existing status +default_output_file: "{output_folder}/bmm-workflow-status.yaml" + +standalone: true diff --git a/.windsurf/workflows/bmad/core/agents/bmad-master.md b/.windsurf/workflows/bmad/core/agents/bmad-master.md new file mode 100644 index 0000000..808c1fb --- /dev/null +++ b/.windsurf/workflows/bmad/core/agents/bmad-master.md @@ -0,0 +1,14 @@ +--- +description: bmad-master +auto_execution_mode: 3 +--- + +You must fully embody this agent's persona and follow all activation instructions exactly as specified. NEVER break character until given an exit command. + + +1. LOAD the FULL agent file from @_bmad/core/agents/bmad-master.md +2. READ its entire contents - this contains the complete agent persona, menu, and instructions +3. Execute ALL activation steps exactly as written in the agent file +4. Follow the agent's persona and menu system precisely +5. Stay in character throughout the session + diff --git a/.windsurf/workflows/bmad/core/tasks/advanced-elicitation.md b/.windsurf/workflows/bmad/core/tasks/advanced-elicitation.md new file mode 100644 index 0000000..cd79c16 --- /dev/null +++ b/.windsurf/workflows/bmad/core/tasks/advanced-elicitation.md @@ -0,0 +1,121 @@ +--- +description: task-advanced-elicitation +auto_execution_mode: 2 +--- + + + + MANDATORY: Execute ALL steps in the flow section IN EXACT ORDER + DO NOT skip steps or change the sequence + HALT immediately when halt-conditions are met + Each action xml tag within step xml tag is a REQUIRED action to complete that step + Sections outside flow (validation, output, critical-context) provide essential context - review and apply throughout execution + + + + When called during template workflow processing: + 1. Receive or review the current section content that was just generated or + 2. Apply elicitation methods iteratively to enhance that specific content + 3. Return the enhanced version back when user selects 'x' to proceed and return back + 4. The enhanced content replaces the original section content in the output document + + + + + Load and read {{methods}} and {{agent-party}} + + + category: Method grouping (core, structural, risk, etc.) + method_name: Display name for the method + description: Rich explanation of what the method does, when to use it, and why it's valuable + output_pattern: Flexible flow guide using → arrows (e.g., "analysis → insights → action") + + + + Use conversation history + Analyze: content type, complexity, stakeholder needs, risk level, and creative potential + + + + 1. Analyze context: Content type, complexity, stakeholder needs, risk level, creative potential + 2. Parse descriptions: Understand each method's purpose from the rich descriptions in CSV + 3. Select 5 methods: Choose methods that best match the context based on their descriptions + 4. Balance approach: Include mix of foundational and specialized techniques as appropriate + + + + + + + **Advanced Elicitation Options (If you launched Party Mode, they will participate randomly)** + Choose a number (1-5), [r] to Reshuffle, [a] List All, or [x] to Proceed: + + 1. [Method Name] + 2. [Method Name] + 3. [Method Name] + 4. [Method Name] + 5. [Method Name] + r. Reshuffle the list with 5 new options + a. List all methods with descriptions + x. Proceed / No Further Actions + + + + + Execute the selected method using its description from the CSV + Adapt the method's complexity and output format based on the current context + Apply the method creatively to the current section content being enhanced + Display the enhanced version showing what the method revealed or improved + CRITICAL: Ask the user if they would like to apply the changes to the doc (y/n/other) and HALT to await response. + CRITICAL: ONLY if Yes, apply the changes. IF No, discard your memory of the proposed changes. If any other reply, try best to + follow the instructions given by the user. + CRITICAL: Re-present the same 1-5,r,x prompt to allow additional elicitations + + + Select 5 random methods from advanced-elicitation-methods.csv, present new list with same prompt format + When selecting, try to think and pick a diverse set of methods covering different categories and approaches, with 1 and 2 being + potentially the most useful for the document or section being discovered + + + Complete elicitation and proceed + Return the fully enhanced content back to create-doc.md + The enhanced content becomes the final version for that section + Signal completion back to create-doc.md to continue with next section + + + List all methods with their descriptions from the CSV in a compact table + Allow user to select any method by name or number from the full list + After selection, execute the method as described in the n="1-5" case above + + + Apply changes to current section content and re-present choices + + + Execute methods in sequence on the content, then re-offer choices + + + + + + Method execution: Use the description from CSV to understand and apply each method + Output pattern: Use the pattern as a flexible guide (e.g., "paths → evaluation → selection") + Dynamic adaptation: Adjust complexity based on content needs (simple to sophisticated) + Creative application: Interpret methods flexibly based on context while maintaining pattern consistency + Focus on actionable insights + Stay relevant: Tie elicitation to specific content being analyzed (the current section from the document being created unless user + indicates otherwise) + Identify personas: For single or multi-persona methods, clearly identify viewpoints, and use party members if available in memory + already + Critical loop behavior: Always re-offer the 1-5,r,a,x choices after each method execution + Continue until user selects 'x' to proceed with enhanced content, confirm or ask the user what should be accepted from the session + Each method application builds upon previous enhancements + Content preservation: Track all enhancements made during elicitation + Iterative enhancement: Each selected method (1-5) should: + 1. Apply to the current enhanced version of the content + 2. Show the improvements made + 3. Return to the prompt for additional elicitations or completion + + + \ No newline at end of file diff --git a/.windsurf/workflows/bmad/core/tasks/index-docs.md b/.windsurf/workflows/bmad/core/tasks/index-docs.md new file mode 100644 index 0000000..e1ad835 --- /dev/null +++ b/.windsurf/workflows/bmad/core/tasks/index-docs.md @@ -0,0 +1,70 @@ +--- +description: task-index-docs +auto_execution_mode: 2 +--- + + + + MANDATORY: Execute ALL steps in the flow section IN EXACT ORDER + DO NOT skip steps or change the sequence + HALT immediately when halt-conditions are met + Each action xml tag within step xml tag is a REQUIRED action to complete that step + Sections outside flow (validation, output, critical-context) provide essential context - review and apply throughout execution + + + + + List all files and subdirectories in the target location + + + + Organize files by type, purpose, or subdirectory + + + + Read each file to understand its actual purpose and create brief (3-10 word) descriptions based on the content, not just the + filename + + + + Write or update index.md with organized file listings + + + + + + # Directory Index + + ## Files + + - **[filename.ext](./filename.ext)** - Brief description + - **[another-file.ext](./another-file.ext)** - Brief description + + ## Subdirectories + + ### subfolder/ + + - **[file1.ext](./subfolder/file1.ext)** - Brief description + - **[file2.ext](./subfolder/file2.ext)** - Brief description + + ### another-folder/ + + - **[file3.ext](./another-folder/file3.ext)** - Brief description + + + + + HALT if target directory does not exist or is inaccessible + HALT if user does not have write permissions to create index.md + + + + Use relative paths starting with ./ + Group similar files together + Read file contents to generate accurate descriptions - don't guess from filenames + Keep descriptions concise but informative (3-10 words) + Sort alphabetically within groups + Skip hidden files (starting with .) unless specified + + \ No newline at end of file diff --git a/.windsurf/workflows/bmad/core/tools/shard-doc.md b/.windsurf/workflows/bmad/core/tools/shard-doc.md new file mode 100644 index 0000000..286e2c5 --- /dev/null +++ b/.windsurf/workflows/bmad/core/tools/shard-doc.md @@ -0,0 +1,114 @@ +--- +description: tool-shard-doc +auto_execution_mode: 2 +--- + + + Split large markdown documents into smaller, organized files based on level 2 sections using @kayvan/markdown-tree-parser tool + + + MANDATORY: Execute ALL steps in the flow section IN EXACT ORDER + DO NOT skip steps or change the sequence + HALT immediately when halt-conditions are met + Each action xml tag within step xml tag is a REQUIRED action to complete that step + Sections outside flow (validation, output, critical-context) provide essential context - review and apply throughout execution + + + + Uses `npx @kayvan/markdown-tree-parser` to automatically shard documents by level 2 headings and generate an index + + + + + Ask user for the source document path if not provided already + Verify file exists and is accessible + Verify file is markdown format (.md extension) + HALT with error message + + + + Determine default destination: same location as source file, folder named after source file without .md extension + Example: /path/to/architecture.md → /path/to/architecture/ + Ask user for the destination folder path ([y] to confirm use of default: [suggested-path], else enter a new path) + Use the suggested destination path + Use the custom destination path + Verify destination folder exists or can be created + Check write permissions for destination + HALT with error message + + + + Inform user that sharding is beginning + Execute command: `npx @kayvan/markdown-tree-parser explode [source-document] [destination-folder]` + Capture command output and any errors + HALT and display error to user + + + + Check that destination folder contains sharded files + Verify index.md was created in destination folder + Count the number of files created + HALT with error message + + + + Display completion report to user including: + - Source document path and name + - Destination folder path + - Number of section files created + - Confirmation that index.md was created + - Any tool output or warnings + Inform user that sharding completed successfully + + + + Keeping both the original and sharded versions defeats the purpose of sharding and can cause confusion + Present user with options for the original document: + + What would you like to do with the original document `[source-document-name]`? + + Options: + [d] Delete - Remove the original (recommended - shards can always be recombined) + [m] Move to archive - Move original to a backup/archive location + [k] Keep - Leave original in place (NOT recommended - defeats sharding purpose) + + Your choice (d/m/k): + + + Delete the original source document file + Confirm deletion to user: "✓ Original document deleted: [source-document-path]" + The document can be reconstructed from shards by concatenating all section files in order + + + + Determine default archive location: same directory as source, in an "archive" subfolder + Example: /path/to/architecture.md → /path/to/archive/architecture.md + Archive location ([y] to use default: [default-archive-path], or provide custom path): + Use default archive path + Use custom archive path + Create archive directory if it doesn't exist + Move original document to archive location + Confirm move to user: "✓ Original document moved to: [archive-path]" + + + + Display warning to user: + ⚠️ WARNING: Keeping both original and sharded versions is NOT recommended. + + This creates confusion because: + - The discover_inputs protocol may load the wrong version + - Updates to one won't reflect in the other + - You'll have duplicate content taking up space + + Consider deleting or archiving the original document. + Confirm user choice: "Original document kept at: [source-document-path]" + + + + + + HALT if npx command fails or produces no output files + + \ No newline at end of file diff --git a/DEPLOY.md b/DEPLOY.md new file mode 100644 index 0000000..b442b6e --- /dev/null +++ b/DEPLOY.md @@ -0,0 +1,255 @@ +# Deploy Guide + +> Step-by-step guide for deploying this boilerplate to any VPS via Docker + Coolify. +> Designed for AI agents to follow without errors. + +## Architecture + +``` +Build Machine (Mac/CI) → Docker Image → Registry → Coolify Service + ├── web (Next.js) + ├── db (PostgreSQL + pgvector) + └── minio (S3 storage) +``` + +- **Build**: Cross-compile locally (ARM Mac → AMD64 server) +- **Registry**: Gitea container registry (or any Docker registry) +- **Runtime**: Coolify manages docker-compose service +- **TLS**: Handled externally (Tailscale Funnel, Cloudflare, etc.) + +## Prerequisites + +- Docker Desktop with `linux/amd64` platform support +- SSH access to the target server +- Coolify running on the server +- A Docker registry (Gitea, Docker Hub, GHCR, etc.) + +## Step 1: Configure Environment + +```bash +# Copy the production template +cp .env.production.example .env.production + +# Generate auth secret +openssl rand -base64 32 +# Paste into BETTER_AUTH_SECRET= + +# Fill in required values: +# - DATABASE_URL (will be set in docker-compose, but needed for schema push) +# - BETTER_AUTH_SECRET (generated above) +# - NEXT_PUBLIC_URL (your public URL) +# - BETTER_AUTH_TRUSTED_ORIGINS (same as public URL) +``` + +See `.env.production.example` for full list with `[REQUIRED]` / `[FEATURE]` / `[OPTIONAL]` tags. + +## Step 2: Build & Push Image + +```bash +# Login to your registry (adjust for your setup) +docker login -u + +# Build for AMD64 (required for most VPS) +docker build --platform linux/amd64 \ + --build-arg NEXT_PUBLIC_URL=https://your-app.example.com \ + -t //:latest . + +# Push +docker push //:latest +``` + +Build takes ~2 min on Mac M-series. If push fails with EOF, retry. + +## Step 3: Create Coolify Service + +Create a Coolify **service** (not application) with this docker-compose template. +Replace all `` with your values: + +```yaml +services: + web: + image: //:latest + restart: always + environment: + - NODE_ENV=production + - PORT=3000 + - HOSTNAME=0.0.0.0 + - DATABASE_URL=postgres://:@db:5432/ + - BETTER_AUTH_SECRET= + - BETTER_AUTH_TRUSTED_ORIGINS=https://your-app.example.com + # Optional features — remove if not using: + - S3_BUCKET= + - S3_REGION=us-east-1 + - S3_ENDPOINT=http://minio:9000 + - S3_ACCESS_KEY_ID= + - S3_SECRET_ACCESS_KEY= + ports: + - "3000" + depends_on: + db: + condition: service_healthy + + db: + image: pgvector/pgvector:pg17 + restart: always + environment: + POSTGRES_USER: + POSTGRES_PASSWORD: + POSTGRES_DB: + volumes: + - app-postgres:/var/lib/postgresql/data + healthcheck: + test: ["CMD", "pg_isready", "-U", ""] + interval: 10s + timeout: 5s + retries: 5 + + minio: + image: minio/minio:latest + restart: always + command: server /data --console-address ":9001" + environment: + MINIO_ROOT_USER: + MINIO_ROOT_PASSWORD: + volumes: + - app-minio:/data + healthcheck: + test: ["CMD", "mc", "ready", "local"] + interval: 10s + timeout: 5s + retries: 5 + + minio-init: + image: minio/mc:latest + restart: "no" + depends_on: + minio: + condition: service_healthy + entrypoint: > + /bin/sh -c " + mc alias set myminio http://minio:9000 ; + mc mb myminio/ --ignore-existing; + mc anonymous set download myminio/; + exit 0; + " + +volumes: + app-postgres: + app-minio: +``` + +**If you don't need S3/MinIO**: Remove `minio`, `minio-init` services and all `S3_*` env vars. + +## Step 4: Set FQDN in Coolify + +Set the web sub-application's FQDN. Use **HTTP** if TLS is handled externally (Tailscale, Cloudflare): + +```bash +ssh "docker exec coolify php artisan tinker --execute=\" +use App\Models\ServiceApplication; +\\\$app = ServiceApplication::where('service_id', )->where('name', 'web')->first(); +\\\$app->fqdn = 'http://your-app.example.com'; +\\\$app->save(); +echo 'FQDN: ' . \\\$app->fqdn; +\"" +``` + +## Step 5: Start the Service + +Via Coolify MCP or UI. Wait ~30 seconds for all containers to become healthy. + +## Step 6: Initialize Database + +```bash +# 1. Create schemas (needed for AI features — skip if not using them) +ssh "docker exec psql -U -d -c \ + 'CREATE SCHEMA IF NOT EXISTS chat; CREATE SCHEMA IF NOT EXISTS pdf; CREATE SCHEMA IF NOT EXISTS image;'" + +# 2. Get DB container IP (host can't resolve Docker DNS) +ssh "docker inspect --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'" + +# 3. SSH tunnel +ssh -f -N -L 5440::5432 + +# 4. Push schema (MUST run from packages/db directory) +cd packages/db +DATABASE_URL="postgres://:@localhost:5440/" npx drizzle-kit push --force + +# 5. Seed users (run from packages/auth directory) +cd ../auth +SKIP_ENV_VALIDATION=1 \ +DATABASE_URL="postgres://:@localhost:5440/" \ +BETTER_AUTH_SECRET="" \ +npx tsx ./src/scripts/seed.ts + +# 6. Kill tunnel +pkill -f "ssh -f -N -L 5440" +``` + +## Step 7: Verify + +Open your app URL. Sign in with: +- Email: value of `SEED_EMAIL` (default: `me@turbostarter.dev`) +- Password: value of `SEED_PASSWORD` (default: `Pa$$w0rd`) + +--- + +## Redeploy (After Code Changes) + +```bash +# 1. Build & push new image +docker build --platform linux/amd64 \ + --build-arg NEXT_PUBLIC_URL=https://your-app.example.com \ + -t //:latest . +docker push //:latest + +# 2. Pull new image on the server (required — Coolify won't pull automatically) +ssh "docker pull localhost://:latest" + +# 3. Restart via Coolify (stop + start, or MCP restart) +``` + +**If containers get stuck in "Created" state after restart:** +```bash +# Coolify sometimes leaves containers in "Created" state after stop+start. +# Fix: manually start them in dependency order: +ssh "docker start " +# Wait ~15s for healthchecks, then: +ssh "docker start " + +# Or nuclear option: Coolify stop, then start again (creates fresh containers) +``` + +--- + +## Runtime Env Validation + +The app validates environment variables **at startup** (not build time): + +| Category | Behavior | +|----------|----------| +| `DATABASE_URL`, `BETTER_AUTH_SECRET` | **Required** — app exits with clear error if missing | +| S3 vars (when `S3_BUCKET` is set) | **Feature-gated** — required only when feature is enabled | +| Stripe vars (when `STRIPE_SECRET_KEY` is set) | **Feature-gated** — required only when feature is enabled | +| Email vars (when provider key is set) | **Feature-gated** — required only when feature is enabled | +| `BETTER_AUTH_TRUSTED_ORIGINS` | **Warning** — app starts but logs a warning | +| Monitoring, analytics | **Optional** — silently disabled if not set | + +This means: **if the app starts, it's fully configured**. No silent failures. + +--- + +## Critical Rules + +1. **Image name in compose**: Use `localhost:/...` — not the external IP (avoids HTTPS errors) +2. **FQDN must be `http://`** when TLS is handled externally (Tailscale, Cloudflare) +3. **`minio-init` must have `restart: "no"`** — Coolify adds `unless-stopped` by default +4. **Healthcheck uses `node -e "fetch(...)"`** — `node:22-slim` has no wget/curl +5. **`NEXT_PUBLIC_URL` is a build arg** — baked at compile time, must rebuild to change +6. **`BETTER_AUTH_TRUSTED_ORIGINS` is runtime** — comma-separated allowed origins +7. **drizzle-kit runs from `packages/db/`** — not from repo root +8. **SSH tunnel uses container IP** — not container name (host can't resolve Docker DNS) +9. **Seed script is at `packages/auth/`** — `packages/db/` seed is a placeholder +10. **Don't build on small VPS** — cross-compile locally to avoid OOM +11. **Pull image on server before restarting** — Coolify won't auto-pull from local registries +12. **Containers stuck in "Created"** — Coolify bug; manually `docker start` in dependency order (db → minio → web) diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ef3fcb1 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,49 @@ +# Turbostarter Production Dockerfile +# Single-stage build (mimics nixpacks) + slim production image +# Build locally on Mac, push to Gitea registry, deploy via Coolify + +# Stage 1: Build everything in one layer (like nixpacks does) +FROM node:22-slim AS builder +WORKDIR /app + +# Install pnpm +RUN corepack enable && corepack prepare pnpm@10.25.0 --activate + +# Copy everything (pnpm workspaces need full context for resolution) +COPY . . + +# Install all dependencies (hoisted, same as nixpacks) +RUN pnpm install --frozen-lockfile + +# Build — SKIP_ENV_VALIDATION=1 so missing runtime vars don't block build +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 +ENV SKIP_ENV_VALIDATION=1 +ARG NEXT_PUBLIC_URL=http://localhost:3000 +ENV NEXT_PUBLIC_URL=$NEXT_PUBLIC_URL +RUN npx turbo run build + +# Stage 2: Minimal production image +FROM node:22-slim AS runner +WORKDIR /app + +ENV NODE_ENV=production +ENV NEXT_TELEMETRY_DISABLED=1 + +RUN addgroup --system --gid 1001 nodejs && \ + adduser --system --uid 1001 nextjs + +# Copy standalone output from builder +COPY --from=builder /app/apps/web/.next/standalone ./ +COPY --from=builder /app/apps/web/.next/static ./apps/web/.next/static +COPY --from=builder /app/apps/web/public ./apps/web/public + +USER nextjs +EXPOSE 3000 +ENV PORT=3000 +ENV HOSTNAME="0.0.0.0" + +HEALTHCHECK --interval=15s --timeout=5s --start-period=10s --retries=3 \ + CMD node -e "fetch('http://localhost:3000').then(r=>{process.exit(r.ok?0:1)}).catch(()=>process.exit(1))" + +CMD ["node", "apps/web/server.js"] diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..b02b706 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,164 @@ +--- +title: EULA (End User License Agreement) +description: Information about the license for TurboStarter's services. +--- + +## TL;DR + +This summary is for convenience only. If anything here differs from the EULA, the EULA controls. + +**You can:** + +- Use the Software on multiple devices for yourself or your company +- Build and ship unlimited End Products (commercial or free) +- Sell and distribute your End Products to customers or users +- Modify the code solely to build those End Products +- Use the Software for unlimited client projects, as long as the client does not receive the Software or its source unless they buy their own license +- Team use with one license (seat) per individual user (including contractors) +- Allow employees and contractors to work with the Software on your behalf under confidentiality, provided each individual has their own license (seat) +- Publish an open-source End Product only with prior written approval from the Licensor + +**You can't:** + +- Redistribute, resell, or share the Software or its source as a template/starter/boilerplate +- Give the Software or its source code to a client or any third party who doesn’t have their own license +- Transfer, assign, or sublicense your license +- Create a competing product or starter substantially based on this Software +- Remove copyright, trademark, or proprietary notices +- Reverse engineer, decompile, or circumvent protections +- Use the Software for illegal purposes + +Bartosz Zagrodzki ("**Licensor**") grants you ("**Licensee**") a non-exclusive, non-transferable, revocable license to use the TurboStarter download files ("**Software**") subject to the terms and conditions below. By purchasing a license or accessing the Software, you agree to be bound by this EULA. + +## 1. Definitions + +- **"Licensor"** means Bartosz Zagrodzki, the owner and provider of the Software. + +- **"Licensee"** means you as an individual or a single legal entity (business, organization, or company) that has purchased a license to the Software. + +- **"Software"** means the TurboStarter codebase, including all files, source code, executable code, documentation, and any updates, patches, or modifications provided by Licensor, delivered in any form. + +- **"End Product"** means any application, website, service, system, or other artifact produced by Licensee, for itself or for its clients, that incorporates, incorporates derivatives of, or is created using the Software as a foundation. + +- **"Documentation"** means all written materials, guides, tutorials, and online content provided by Licensor relating to the use and functionality of the Software. + +- **"Intellectual Property Rights"** means all copyright, trademark, patent, moral rights, design rights, and trade secret rights, whether registered or unregistered, in the Software and all modifications, improvements, and enhancements thereto. + +- **"License"** means the non-exclusive, non-transferable, revocable right granted by this Agreement to use the Software under the stated terms and conditions. + +- **"Confidential Information"** means proprietary information contained in the Software, including trade secrets, algorithms, architecture, and design patterns not publicly available. + +- **"Term"** means the period during which this License is valid, commencing upon acceptance of this EULA and continuing unless terminated as provided herein. + +## 2. License Grant + +Licensor grants Licensee a **non-exclusive, non-transferable, revocable, personal license** to: + +- Install and use the Software on multiple devices for Licensee's own use +- Create unlimited End Products incorporating the Software +- Sell or distribute End Products to end users +- Modify the Software solely for creating End Products +- Create open-source End Products with prior written approval from Licensor +- Use the Software to create End Products for unlimited clients as part of services provided by Licensee, provided the Software itself (including its source code) is not distributed or made available as a standalone deliverable to those clients unless they separately purchase their own license +- Permit Licensee's employees and contractors to access and use the Software solely on Licensee's behalf to develop End Products for Licensee or its clients, provided each such individual holds their own valid license (seat) purchased from Licensor and is bound by confidentiality and use restrictions no less protective than this EULA + +This license is granted only to the individual or legal entity listed as the Licensee and may not be shared, transferred, or used by any other person or entity. + +Team/Seat Licensing: If the Software is used by a team, you must purchase one license (seat) for each individual who accesses the Software, including employees and contractors. Seats are assigned to named individuals and are not transferable between different people. + +## 3. Restrictions + +Licensee may **not**: + +- Redistribute, sell, or license the Software itself as a standalone product +- Transfer, assign, sublicense, or share this License with any third party +- Reverse engineer, decompile, disassemble, or attempt to derive the source code of the Software +- Remove, obscure, or alter any copyright, trademark, or proprietary notices in the Software +- Use the Software for illegal purposes or in violation of any applicable law +- Create a competing product using substantially similar code or design patterns from the Software +- Sublicense, share, or provide the Software or its source code to clients or any third party, except where such party has purchased its own license from Licensor +- Distribute the Software as a template, starter, or boilerplate intended for reuse by parties other than Licensee, whether or not for a fee +- Share a single license among multiple individuals; seat-sharing is prohibited + +## 4. Ownership and Intellectual Property Rights + +Licensor retains all Intellectual Property Rights in the Software, including all copies, modifications, improvements, and derivatives thereof. Licensee owns the End Products created by Licensee, but Licensor retains all ownership of the underlying Software components within those End Products. The license granted herein does not transfer any ownership rights to Licensee. + +## 5. Warranty Disclaimer + +**THE SOFTWARE IS PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED.** LICENSOR EXPRESSLY DISCLAIMS ALL WARRANTIES, INCLUDING BUT NOT LIMITED TO: + +- Warranties of **merchantability**, fitness for a **particular purpose**, or non-infringement +- Any warranty that the Software will meet Licensee's requirements +- Any warranty that the Software will operate without error, interruption, or defects +- Any warranty regarding the accuracy, completeness, or reliability of the Software + +Licensor makes no representations that the Software is free of viruses, malware, or other harmful components. **Licensee assumes all responsibility for the consequences of using the Software.** + +## 6. Limitation of Liability + +**TO THE MAXIMUM EXTENT PERMITTED BY LAW, LICENSOR SHALL NOT BE LIABLE FOR:** + +- **Indirect, incidental, special, consequential, or punitive damages**, including loss of profits, loss of data, loss of business opportunity, or loss of use +- **Any damages arising from:** use of the Software, inability to use the Software, unauthorized access, data breaches, or performance failures +- **Any liability exceeding the amount paid by Licensee for the license** + +This limitation of liability applies **regardless of whether liability is based on contract, tort, strict liability, negligence, or any other legal theory, and even if Licensor has been advised of the possibility of such damages.** + +**This limitation is fundamental to the pricing of the License and represents an essential condition of this Agreement.** + +## 7. Indemnification + +Licensee agrees to **indemnify, defend, and hold harmless** Licensor from any claims, damages, losses, costs, or attorneys' fees arising from: + +- Licensee's use of the Software in violation of this EULA +- Licensee's modification, misuse, or unauthorized distribution of the Software +- Third-party claims arising from End Products created by Licensee +- Licensee's breach of applicable law while using the Software + +## 8. Termination + +This License **terminates immediately** if Licensee: + +- Breaches any material term of this EULA and does not cure the breach within **14 days** of written notice +- Attempts to reverse engineer, decompile, or circumvent the Software +- Transfers or attempts to transfer the License to another party + +Either party may terminate this License for any reason or no reason by providing **30 days' written notice** to the other party. + +Upon termination: + +- Licensee must immediately cease all use of the Software +- End Products created prior to termination may continue to operate +- All copies of the Software in Licensee's possession must be destroyed or deleted +- Sections 1, 3, 4, 5, 6, 7, and 9 survive termination + +## 9. Governing Law and Jurisdiction + +This EULA is **governed by and construed in accordance with the laws of Poland**, excluding conflict of law principles. + +**Any legal action or proceeding arising from this EULA shall be resolved exclusively in the competent courts of Poland.** + +Licensee consents to the personal jurisdiction of such courts and waives any objection to venue. + +## 10. Entire Agreement + +This EULA, together with any terms posted on Licensor's website, constitutes the **entire agreement** between the parties regarding the Software and supersedes all prior agreements, understandings, and representations. + +**No modification or amendment is valid unless in writing and signed by an authorized representative of Licensor.** + +## 11. Severability + +If any provision of this EULA is held to be invalid, illegal, or unenforceable by a court of competent jurisdiction, such provision shall be severed to the extent of invalidity, and the remaining provisions shall continue in full force and effect. The parties agree to negotiate in good faith to replace any severed provision with a valid provision that achieves the original economic intent. + +## 12. Waiver + +The failure of Licensor to enforce any right, power, or provision of this EULA shall not operate as a waiver of that right, power, or provision. No single or partial waiver shall constitute a waiver of any other or subsequent breach or failure. + +## 13. Contact + +For questions, concerns, or requests regarding this License, contact: **[hello@turbostarter.dev](mailto:hello@turbostarter.dev)** + +--- + +**BY USING, DOWNLOADING, OR INSTALLING THE SOFTWARE, LICENSEE ACKNOWLEDGES HAVING READ THIS EULA AND AGREEING TO BE BOUND BY ALL ITS TERMS AND CONDITIONS.** diff --git a/README.md b/README.md new file mode 100644 index 0000000..2ffd66a --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +
+ +# TurboStarter + +Ship your startup everywhere. In minutes. + +[📚 Documentation](https://www.turbostarter.dev/docs/web)     [💬 Discord](https://discord.gg/KjpK2uk3JP)     [🚀 Demo](https://www.demo.turbostarter.dev) + +
diff --git a/apps/mobile/.env.example b/apps/mobile/.env.example new file mode 100644 index 0000000..b21128a --- /dev/null +++ b/apps/mobile/.env.example @@ -0,0 +1,54 @@ +# Since .env is gitignored, you can use .env.example to build a new `.env` file when you clone the repo. +# Keep this file up-to-date when you add new variables to \`.env\`. + +# This file will be committed to version control, so make sure not to have any secrets in it. +# If you are cloning this repo, create a copy of this file named `.env` and populate it with your secrets. + +# Env variables extracted from shared to be exposed to the client in Expo mobile app +EXPO_PUBLIC_SITE_URL="${URL}" +EXPO_PUBLIC_DEFAULT_LOCALE="${DEFAULT_LOCALE}" + +# Theme mode and color +EXPO_PUBLIC_THEME_MODE="system" +EXPO_PUBLIC_THEME_COLOR="orange" + + +######################### +### Auth config ### +######################### + +# Use this variable to enable or disable password-based authentication. If you set this to true, users will be able to sign up and sign in using their email and password. If you set this to false, the form won't be shown. +EXPO_PUBLIC_AUTH_PASSWORD="true" + +# Use this variable to enable or disable magic link-based authentication. If you set this to true, users will be able to sign up and sign in using a magic link sent to their email. If you set this to false, the form won't be shown. +EXPO_PUBLIC_AUTH_MAGIC_LINK="false" + +# Use this variable to enable or disable anonymous authentication. If you set this to true, users will be able to proceed to your app without "traditional" authentication. If you set this to false, the anonymous login won't be available. +EXPO_PUBLIC_AUTH_ANONYMOUS="true" + +# Required for native "Sign in with Google" on Android, you can reuse the GOOGLE_CLIENT_ID from the web environment variables here +EXPO_PUBLIC_GOOGLE_CLIENT_ID="" + + +############################# +### Monitoring config ### +############################# + +# Sentry config - required only if you use Sentry as a monitoring provider +EXPO_PUBLIC_SENTRY_DSN="" +EXPO_PUBLIC_SENTRY_ENVIRONMENT="development" +SENTRY_AUTH_TOKEN="" # required for source maps upload + +# Posthog config - reused from analytics section below + + +############################ +### Analytics config ### +############################ + +# Posthog config - required only if you use Posthog as an analytics provider +EXPO_PUBLIC_POSTHOG_KEY="" +EXPO_PUBLIC_POSTHOG_HOST="https://us.i.posthog.com" + +# Mixpanel config - required only if you use Mixpanel as an analytics provider +EXPO_PUBLIC_MIXPANEL_TOKEN="" \ No newline at end of file diff --git a/apps/mobile/app.config.ts b/apps/mobile/app.config.ts new file mode 100644 index 0000000..8fcca9d --- /dev/null +++ b/apps/mobile/app.config.ts @@ -0,0 +1,87 @@ +import type { ConfigContext, ExpoConfig } from "expo/config"; + +const SPLASH = { + imageWidth: 150, + image: "./public/images/splash/splash.png", + dark: { + image: "./public/images/splash/splash.png", + backgroundColor: "#0D121C", + }, +} as const; + +export default ({ config }: ConfigContext): ExpoConfig => ({ + ...config, + name: "TurboStarter", + slug: "turbostarter", + scheme: "turbostarter", + version: "1.1.0", + orientation: "portrait", + icon: "./public/images/icon/ios.png", + userInterfaceStyle: "automatic", + assetBundlePatterns: ["**/*"], + platforms: ["ios", "android"], + updates: { + url: "https://u.expo.dev/a7958179-7450-4e6f-8791-da222215909e", + }, + newArchEnabled: true, + ios: { + bundleIdentifier: "com.turbostarter.core", + supportsTablet: true, + usesAppleSignIn: true, + infoPlist: { + ITSAppUsesNonExemptEncryption: false, + }, + }, + android: { + package: "com.turbostarter.core", + adaptiveIcon: { + monochromeImage: "./public/images/icon/android/monochrome.png", + foregroundImage: "./public/images/icon/android/adaptive.png", + backgroundColor: "#0D121C", + }, + edgeToEdgeEnabled: true, + }, + extra: { + eas: { + projectId: "a7958179-7450-4e6f-8791-da222215909e", + }, + }, + experiments: { + tsconfigPaths: true, + typedRoutes: true, + }, + runtimeVersion: { + policy: "appVersion", + }, + plugins: [ + "expo-router", + "expo-font", + "expo-secure-store", + "expo-image-picker", + "expo-web-browser", + "expo-apple-authentication", + "@react-native-google-signin/google-signin", + /* required to enable i18n */ + "expo-localization", + [ + "expo-build-properties", + { + android: { + /* https://github.com/expo/expo/issues/15761 */ + enableProguardInReleaseBuilds: true, + extraProguardRules: "-keep public class com.horcrux.svg.** {*;}", + allowBackup: false, + }, + }, + ], + [ + "expo-tracking-transparency", + { + /* 🍎 Describe why you need access to the user's data */ + userTrackingPermission: + "This identifier will be used to deliver personalized ads to you.", + }, + ], + ["expo-splash-screen", SPLASH], + ], +}); diff --git a/apps/mobile/babel.config.js b/apps/mobile/babel.config.js new file mode 100644 index 0000000..3f060dd --- /dev/null +++ b/apps/mobile/babel.config.js @@ -0,0 +1,8 @@ +/** @type {import("@babel/core").ConfigFunction} */ +module.exports = (api) => { + api.cache(true); + return { + presets: [["babel-preset-expo", { unstable_transformImportMeta: true }]], + plugins: ["react-native-worklets/plugin"], + }; +}; diff --git a/apps/mobile/eas.json b/apps/mobile/eas.json new file mode 100644 index 0000000..6251816 --- /dev/null +++ b/apps/mobile/eas.json @@ -0,0 +1,76 @@ +{ + "cli": { + "appVersionSource": "remote", + "version": ">= 4.1.2" + }, + "build": { + "base": { + "node": "22.17.0", + "pnpm": "10.25.0", + "ios": { + "resourceClass": "m-medium" + }, + "env": { + "EXPO_PUBLIC_DEFAULT_LOCALE": "en", + "EXPO_PUBLIC_AUTH_PASSWORD": "true", + "EXPO_PUBLIC_AUTH_MAGIC_LINK": "true", + "EXPO_PUBLIC_AUTH_ANONYMOUS": "true", + "EXPO_PUBLIC_THEME_MODE": "system", + "EXPO_PUBLIC_THEME_COLOR": "orange" + } + }, + // Development profile used for local development + "development": { + "extends": "base", + "developmentClient": true, + "distribution": "internal", + "environment": "development", + "env": { + "APP_ENV": "development" + } + }, + // Needed for iOS, as you can't reuse `development` profile there for simulator + "development-simulator": { + "extends": "base", + "developmentClient": true, + "distribution": "internal", + "ios": { + "simulator": true + }, + "environment": "development", + "env": { + "APP_ENV": "development" + } + }, + // Preview profile used for EAS test builds in preview environment + "preview": { + "extends": "base", + "distribution": "internal", + "android": { + "buildType": "apk" + }, + "channel": "preview", + "environment": "preview", + "env": { + "APP_ENV": "preview" + } + }, + // Production profile used for EAS production builds to be submitted to the stores + "production": { + "extends": "base", + "autoIncrement": true, + "environment": "production", + "channel": "production", + "env": { + "APP_ENV": "production" + } + } + }, + "submit": { + "production": { + "ios": { + "ascAppId": "6754278899" + } + } + } +} diff --git a/apps/mobile/env.config.ts b/apps/mobile/env.config.ts new file mode 100644 index 0000000..773db18 --- /dev/null +++ b/apps/mobile/env.config.ts @@ -0,0 +1,55 @@ +import { defineEnv } from "envin"; +import * as z from "zod"; + +import { preset as analytics } from "@turbostarter/analytics-mobile/env"; +import { preset as monitoring } from "@turbostarter/monitoring-mobile/env"; +import { envConfig } from "@turbostarter/shared/constants"; +import { ThemeColor, ThemeMode } from "@turbostarter/ui"; + +const castStringToBool = z.preprocess((val) => { + if (typeof val === "string") { + if (["1", "true"].includes(val.toLowerCase())) return true; + if (["0", "false"].includes(val.toLowerCase())) return false; + } + return val; +}, z.coerce.boolean()); + +export default defineEnv({ + ...envConfig, + extends: [analytics, monitoring], + clientPrefix: "EXPO_PUBLIC_", + client: { + EXPO_PUBLIC_AUTH_PASSWORD: castStringToBool.optional().default(true), + EXPO_PUBLIC_AUTH_MAGIC_LINK: castStringToBool.optional().default(false), + EXPO_PUBLIC_AUTH_ANONYMOUS: castStringToBool.optional().default(true), + EXPO_PUBLIC_GOOGLE_CLIENT_ID: z.string().optional().default(""), + + EXPO_PUBLIC_SITE_URL: z.url(), + EXPO_PUBLIC_DEFAULT_LOCALE: z.string().optional().default("en"), + EXPO_PUBLIC_THEME_MODE: z + .enum(ThemeMode) + .optional() + .default(ThemeMode.SYSTEM), + EXPO_PUBLIC_THEME_COLOR: z + .enum(ThemeColor) + .optional() + .default(ThemeColor.ORANGE), + }, + env: { + ...process.env, + EXPO_PUBLIC_SITE_URL: process.env.EXPO_PUBLIC_SITE_URL, + EXPO_PUBLIC_DEFAULT_LOCALE: process.env.EXPO_PUBLIC_DEFAULT_LOCALE, + EXPO_PUBLIC_THEME_MODE: process.env.EXPO_PUBLIC_THEME_MODE, + EXPO_PUBLIC_THEME_COLOR: process.env.EXPO_PUBLIC_THEME_COLOR, + + EXPO_PUBLIC_AUTH_PASSWORD: process.env.EXPO_PUBLIC_AUTH_PASSWORD, + EXPO_PUBLIC_AUTH_MAGIC_LINK: process.env.EXPO_PUBLIC_AUTH_MAGIC_LINK, + EXPO_PUBLIC_AUTH_ANONYMOUS: process.env.EXPO_PUBLIC_AUTH_ANONYMOUS, + EXPO_PUBLIC_GOOGLE_CLIENT_ID: process.env.EXPO_PUBLIC_GOOGLE_CLIENT_ID, + + EXPO_PUBLIC_POSTHOG_KEY: process.env.EXPO_PUBLIC_POSTHOG_KEY, + EXPO_PUBLIC_POSTHOG_HOST: process.env.EXPO_PUBLIC_POSTHOG_HOST, + EXPO_PUBLIC_SENTRY_DSN: process.env.EXPO_PUBLIC_SENTRY_DSN, + EXPO_PUBLIC_SENTRY_ENVIRONMENT: process.env.EXPO_PUBLIC_SENTRY_ENVIRONMENT, + }, +}); diff --git a/apps/mobile/eslint.config.mjs b/apps/mobile/eslint.config.mjs new file mode 100644 index 0000000..8b33446 --- /dev/null +++ b/apps/mobile/eslint.config.mjs @@ -0,0 +1,11 @@ +import baseConfig from "@turbostarter/eslint-config/base"; +import reactConfig from "@turbostarter/eslint-config/react"; + +/** @type {import('typescript-eslint').Config} */ +export default [ + { + ignores: [".expo/**", "expo-plugins/**"], + }, + ...baseConfig, + ...reactConfig, +]; diff --git a/apps/mobile/metro.config.js b/apps/mobile/metro.config.js new file mode 100644 index 0000000..05f1d32 --- /dev/null +++ b/apps/mobile/metro.config.js @@ -0,0 +1,67 @@ +// Learn more: https://docs.expo.dev/guides/monorepos/ +const { getDefaultConfig } = require("expo/metro-config"); +const { FileStore } = require("metro-cache"); +const { withUniwindConfig } = require("uniwind/metro"); + +const path = require("path"); + +const config = withTurborepoManagedCache( + withMonorepoPaths( + withUniwindConfig(getDefaultConfig(__dirname), { + cssEntryFile: "./src/assets/styles/globals.css", + }), + ), +); + +const { transformer, resolver } = config; + +config.transformer = { + ...transformer, + babelTransformerPath: require.resolve("react-native-svg-transformer/expo"), +}; +config.resolver = { + ...resolver, + assetExts: resolver.assetExts.filter((ext) => ext !== "svg"), + sourceExts: [...resolver.sourceExts, "svg"], +}; + +module.exports = config; + +/** + * Add the monorepo paths to the Metro config. + * This allows Metro to resolve modules from the monorepo. + * + * @see https://docs.expo.dev/guides/monorepos/#modify-the-metro-config + * @param {import('expo/metro-config').MetroConfig} config + * @returns {import('expo/metro-config').MetroConfig} + */ +function withMonorepoPaths(config) { + const projectRoot = __dirname; + const workspaceRoot = path.resolve(projectRoot, "../.."); + + // #1 - Watch all files in the monorepo + config.watchFolders = [workspaceRoot]; + + // #2 - Resolve modules within the project's `node_modules` first, then all monorepo modules + config.resolver.nodeModulesPaths = [ + path.resolve(projectRoot, "node_modules"), + path.resolve(workspaceRoot, "node_modules"), + ]; + + return config; +} + +/** + * Move the Metro cache to the `.cache/metro` folder. + * If you have any environment variables, you can configure Turborepo to invalidate it when needed. + * + * @see https://turbo.build/repo/docs/reference/configuration#env + * @param {import('expo/metro-config').MetroConfig} config + * @returns {import('expo/metro-config').MetroConfig} + */ +function withTurborepoManagedCache(config) { + config.cacheStores = [ + new FileStore({ root: path.join(__dirname, ".cache/metro") }), + ]; + return config; +} diff --git a/apps/mobile/package.json b/apps/mobile/package.json new file mode 100644 index 0000000..7345f14 --- /dev/null +++ b/apps/mobile/package.json @@ -0,0 +1,105 @@ +{ + "name": "mobile", + "version": "1.1.0", + "private": true, + "main": "expo-router/entry", + "scripts": { + "android": "expo run:android", + "clean": "git clean -xdf .cache .expo .turbo android ios node_modules", + "dev": "expo start --clear", + "dev:android": "expo start --android", + "dev:ios": "expo start --ios", + "format": "prettier --check . --ignore-path ../../.gitignore", + "ios": "expo run:ios", + "lint": "eslint", + "typecheck": "tsc --noEmit" + }, + "prettier": "@turbostarter/prettier-config", + "dependencies": { + "@dev-plugins/react-navigation": "~0.4.0", + "@dev-plugins/react-query": "~0.4.0", + "@expo-google-fonts/geist": "0.4.1", + "@expo-google-fonts/geist-mono": "0.4.1", + "@expo/metro-runtime": "~6.1.2", + "@hookform/resolvers": "5.2.2", + "@react-native-async-storage/async-storage": "2.2.0", + "@react-native-google-signin/google-signin": "16.0.0", + "@shopify/react-native-skia": "2.2.12", + "@stardazed/streams-text-encoding": "1.0.2", + "@tanstack/react-query": "catalog:", + "@turbostarter/analytics-mobile": "workspace:*", + "@turbostarter/api": "workspace:*", + "@turbostarter/auth": "workspace:*", + "@turbostarter/billing": "workspace:*", + "@turbostarter/cms": "workspace:*", + "@turbostarter/db": "workspace:*", + "@turbostarter/i18n": "workspace:*", + "@turbostarter/monitoring-mobile": "workspace:*", + "@turbostarter/shared": "workspace:*", + "@turbostarter/ui": "workspace:*", + "@turbostarter/ui-mobile": "workspace:*", + "@ungap/structured-clone": "1.3.0", + "envin": "catalog:", + "expo": "~54.0.27", + "expo-apple-authentication": "~8.0.8", + "expo-application": "~7.0.8", + "expo-auth-session": "~7.0.10", + "expo-blur": "~15.0.8", + "expo-build-properties": "~1.0.10", + "expo-clipboard": "~8.0.8", + "expo-constants": "~18.0.11", + "expo-crypto": "~15.0.8", + "expo-dev-client": "~6.0.20", + "expo-font": "~14.0.10", + "expo-glass-effect": "~0.1.8", + "expo-image": "~3.0.11", + "expo-image-picker": "~17.0.9", + "expo-linking": "~8.0.10", + "expo-localization": "~17.0.8", + "expo-navigation-bar": "~5.0.10", + "expo-network": "~8.0.8", + "expo-router": "~6.0.17", + "expo-secure-store": "^15.0.8", + "expo-splash-screen": "~31.0.12", + "expo-status-bar": "~3.0.9", + "expo-store-review": "~9.0.9", + "expo-system-ui": "~6.0.9", + "expo-tracking-transparency": "~6.0.8", + "expo-updates": "~29.0.15", + "expo-web-browser": "~15.0.10", + "metro-react-native-babel-transformer": "0.77.0", + "react": "catalog:react19", + "react-dom": "catalog:react19", + "react-hook-form": "catalog:", + "react-native": "catalog:", + "react-native-gesture-handler": "~2.29.1", + "react-native-keyboard-controller": "1.18.5", + "react-native-marked": "7.0.2", + "react-native-reanimated": "~4.1.5", + "react-native-safe-area-context": "5.6.2", + "react-native-screens": "~4.16.0", + "react-native-svg": "15.15.1", + "react-native-svg-transformer": "1.5.2", + "react-native-web": "~0.21.2", + "react-native-webview": "13.15.0", + "react-native-worklets": "0.5.1", + "uniwind": "1.2.2", + "victory-native": "41.20.1", + "zod": "catalog:", + "zustand": "5.0.8" + }, + "devDependencies": { + "@babel/core": "^7.28.5", + "@babel/preset-env": "^7.28.5", + "@babel/runtime": "^7.28.4", + "@turbostarter/eslint-config": "workspace:*", + "@turbostarter/prettier-config": "workspace:*", + "@turbostarter/tsconfig": "workspace:*", + "@types/babel__core": "^7.20.5", + "@types/react": "catalog:react19", + "@types/ungap__structured-clone": "1.2.0", + "eslint": "catalog:", + "prettier": "catalog:", + "typescript": "catalog:" + } +} diff --git a/apps/mobile/public/images/icon/android/adaptive.png b/apps/mobile/public/images/icon/android/adaptive.png new file mode 100644 index 0000000..6a08bfa Binary files /dev/null and b/apps/mobile/public/images/icon/android/adaptive.png differ diff --git a/apps/mobile/public/images/icon/android/monochrome.png b/apps/mobile/public/images/icon/android/monochrome.png new file mode 100644 index 0000000..71e8896 Binary files /dev/null and b/apps/mobile/public/images/icon/android/monochrome.png differ diff --git a/apps/mobile/public/images/icon/ios.png b/apps/mobile/public/images/icon/ios.png new file mode 100644 index 0000000..04d8c8b Binary files /dev/null and b/apps/mobile/public/images/icon/ios.png differ diff --git a/apps/mobile/public/images/setup/1/dark.png b/apps/mobile/public/images/setup/1/dark.png new file mode 100644 index 0000000..e372148 Binary files /dev/null and b/apps/mobile/public/images/setup/1/dark.png differ diff --git a/apps/mobile/public/images/setup/1/light.png b/apps/mobile/public/images/setup/1/light.png new file mode 100644 index 0000000..1b9b8a8 Binary files /dev/null and b/apps/mobile/public/images/setup/1/light.png differ diff --git a/apps/mobile/public/images/setup/2/dark.png b/apps/mobile/public/images/setup/2/dark.png new file mode 100644 index 0000000..7fbe776 Binary files /dev/null and b/apps/mobile/public/images/setup/2/dark.png differ diff --git a/apps/mobile/public/images/setup/2/light.png b/apps/mobile/public/images/setup/2/light.png new file mode 100644 index 0000000..11e4ff9 Binary files /dev/null and b/apps/mobile/public/images/setup/2/light.png differ diff --git a/apps/mobile/public/images/setup/3/dark.png b/apps/mobile/public/images/setup/3/dark.png new file mode 100644 index 0000000..de09521 Binary files /dev/null and b/apps/mobile/public/images/setup/3/dark.png differ diff --git a/apps/mobile/public/images/setup/3/light.png b/apps/mobile/public/images/setup/3/light.png new file mode 100644 index 0000000..afd7d8c Binary files /dev/null and b/apps/mobile/public/images/setup/3/light.png differ diff --git a/apps/mobile/public/images/splash/splash.png b/apps/mobile/public/images/splash/splash.png new file mode 100644 index 0000000..9aa223c Binary files /dev/null and b/apps/mobile/public/images/splash/splash.png differ diff --git a/apps/mobile/src/app/(setup)/_layout.tsx b/apps/mobile/src/app/(setup)/_layout.tsx new file mode 100644 index 0000000..93ae1ec --- /dev/null +++ b/apps/mobile/src/app/(setup)/_layout.tsx @@ -0,0 +1,14 @@ +import { Stack } from "expo-router"; + +export default function SetupLayout() { + return ( + + ); +} diff --git a/apps/mobile/src/app/(setup)/auth/_layout.tsx b/apps/mobile/src/app/(setup)/auth/_layout.tsx new file mode 100644 index 0000000..c3d8fe0 --- /dev/null +++ b/apps/mobile/src/app/(setup)/auth/_layout.tsx @@ -0,0 +1,24 @@ +import { router, Stack } from "expo-router"; + +import { pathsConfig } from "~/config/paths"; +import { BaseHeader } from "~/modules/common/layout/header"; + +export default function AuthLayout() { + return ( + ( + + router.canGoBack() + ? router.back() + : router.replace(pathsConfig.index) + } + /> + ), + animation: "fade", + animationDuration: 200, + }} + /> + ); +} diff --git a/apps/mobile/src/app/(setup)/auth/error.tsx b/apps/mobile/src/app/(setup)/auth/error.tsx new file mode 100644 index 0000000..d50e20d --- /dev/null +++ b/apps/mobile/src/app/(setup)/auth/error.tsx @@ -0,0 +1,39 @@ +import { useLocalSearchParams } from "expo-router"; +import { View } from "react-native"; + +import { useTranslation } from "@turbostarter/i18n"; +import { Icons } from "@turbostarter/ui-mobile/icons"; +import { Text } from "@turbostarter/ui-mobile/text"; + +import { pathsConfig } from "~/config/paths"; +import { Link } from "~/modules/common/styled"; + +const AuthError = () => { + const { error } = useLocalSearchParams<{ error?: string }>(); + const { t } = useTranslation(["auth", "common"]); + + return ( + + + + {t("error.general")} + + + {error && ( + + {error} + + )} + + + {t("goToLogin")} + + + ); +}; + +export default AuthError; diff --git a/apps/mobile/src/app/(setup)/auth/join.tsx b/apps/mobile/src/app/(setup)/auth/join.tsx new file mode 100644 index 0000000..259da76 --- /dev/null +++ b/apps/mobile/src/app/(setup)/auth/join.tsx @@ -0,0 +1,99 @@ +import { useQuery } from "@tanstack/react-query"; +import { Redirect, useLocalSearchParams } from "expo-router"; +import { View } from "react-native"; + +import { handle } from "@turbostarter/api/utils"; + +import { pathsConfig } from "~/config/paths"; +import { api } from "~/lib/api"; +import { authClient } from "~/lib/auth"; +import { Spinner } from "~/modules/common/spinner"; +import { Invitation } from "~/modules/organization/invitations/invitation"; +import { InvitationEmailMismatch } from "~/modules/organization/invitations/invitation-email-mismatch"; +import { InvitationExpired } from "~/modules/organization/invitations/invitation-expired"; +import { organization } from "~/modules/organization/lib/api"; + +const InvitationCheck = ({ + invitationId, + email, +}: { + invitationId: string; + email?: string; +}) => { + const session = authClient.useSession(); + const invitation = useQuery( + organization.queries.invitations.get({ id: invitationId }), + ); + const invitationOrganization = useQuery({ + queryKey: organization.queries.get({ + id: invitation.data?.organizationId ?? "", + }).queryKey, + queryFn: () => + handle(api.organizations[":id"].$get)({ + param: { id: invitation.data?.organizationId ?? "" }, + }), + enabled: !!invitation.data, + }); + + if (invitation.isLoading || invitationOrganization.isLoading) { + return ; + } + + if (invitation.data && invitationOrganization.data?.organization) { + return ( + + ); + } + + if (email && session.data?.user.email !== email) { + return ( + + ); + } + + return ; +}; + +export default function Join() { + const { invitationId, email } = useLocalSearchParams<{ + invitationId?: string; + email?: string; + }>(); + + const session = authClient.useSession(); + if (session.isPending) { + return ( + + + + ); + } + + if (!invitationId) { + return ; + } + + if (!session.data?.user) { + const searchParams = new URLSearchParams(); + searchParams.set("invitationId", invitationId); + if (email) searchParams.set("email", email); + searchParams.set( + "redirectTo", + `${pathsConfig.setup.auth.join}?${searchParams.toString()}`, + ); + return ( + + ); + } + + return ( + + + + ); +} diff --git a/apps/mobile/src/app/(setup)/auth/login.tsx b/apps/mobile/src/app/(setup)/auth/login.tsx new file mode 100644 index 0000000..3b2a31a --- /dev/null +++ b/apps/mobile/src/app/(setup)/auth/login.tsx @@ -0,0 +1,23 @@ +import { useGlobalSearchParams } from "expo-router"; + +import { LoginFlow } from "~/modules/auth/login"; + +import type { Route } from "expo-router"; + +const LoginPage = () => { + const { redirectTo, invitationId, email } = useGlobalSearchParams<{ + redirectTo?: Route; + invitationId?: string; + email?: string; + }>(); + + return ( + + ); +}; + +export default LoginPage; diff --git a/apps/mobile/src/app/(setup)/auth/password/forgot.tsx b/apps/mobile/src/app/(setup)/auth/password/forgot.tsx new file mode 100644 index 0000000..8ce8c90 --- /dev/null +++ b/apps/mobile/src/app/(setup)/auth/password/forgot.tsx @@ -0,0 +1,20 @@ +import { useTranslation } from "@turbostarter/i18n"; + +import { ForgotPasswordForm } from "~/modules/auth/form/password/forgot"; +import { AuthLayout } from "~/modules/auth/layout/base"; +import { AuthHeader } from "~/modules/auth/layout/header"; + +const ForgotPassword = () => { + const { t } = useTranslation("auth"); + return ( + + + + + ); +}; + +export default ForgotPassword; diff --git a/apps/mobile/src/app/(setup)/auth/password/update.tsx b/apps/mobile/src/app/(setup)/auth/password/update.tsx new file mode 100644 index 0000000..f648abd --- /dev/null +++ b/apps/mobile/src/app/(setup)/auth/password/update.tsx @@ -0,0 +1,24 @@ +import { useLocalSearchParams } from "expo-router"; + +import { useTranslation } from "@turbostarter/i18n"; + +import { UpdatePasswordForm } from "~/modules/auth/form/password/update"; +import { AuthLayout } from "~/modules/auth/layout/base"; +import { AuthHeader } from "~/modules/auth/layout/header"; + +const UpdatePassword = () => { + const { token } = useLocalSearchParams<{ token?: string }>(); + const { t } = useTranslation("auth"); + + return ( + + + + + ); +}; + +export default UpdatePassword; diff --git a/apps/mobile/src/app/(setup)/auth/register.tsx b/apps/mobile/src/app/(setup)/auth/register.tsx new file mode 100644 index 0000000..a59942f --- /dev/null +++ b/apps/mobile/src/app/(setup)/auth/register.tsx @@ -0,0 +1,49 @@ +import { useGlobalSearchParams } from "expo-router"; +import { View } from "react-native"; + +import { useTranslation } from "@turbostarter/i18n"; + +import { authConfig } from "~/config/auth"; +import { AnonymousLogin } from "~/modules/auth/form/anonymous"; +import { LoginCta } from "~/modules/auth/form/login/form"; +import { RegisterForm } from "~/modules/auth/form/register-form"; +import { SocialProviders } from "~/modules/auth/form/social-providers"; +import { AuthLayout } from "~/modules/auth/layout/base"; +import { AuthDivider } from "~/modules/auth/layout/divider"; +import { AuthHeader } from "~/modules/auth/layout/header"; +import { InvitationDisclaimer } from "~/modules/auth/layout/invitation-disclaimer"; + +import type { Route } from "expo-router"; + +const RegisterPage = () => { + const { t } = useTranslation("auth"); + + const { redirectTo, invitationId, email } = useGlobalSearchParams<{ + redirectTo?: Route; + invitationId?: string; + email?: string; + }>(); + + return ( + + + {invitationId && } + + {authConfig.providers.oAuth.length > 0 && } + + + + {authConfig.providers.anonymous && } + + + + ); +}; + +export default RegisterPage; diff --git a/apps/mobile/src/app/(setup)/steps/_layout.tsx b/apps/mobile/src/app/(setup)/steps/_layout.tsx new file mode 100644 index 0000000..b8bfcef --- /dev/null +++ b/apps/mobile/src/app/(setup)/steps/_layout.tsx @@ -0,0 +1,143 @@ +import AsyncStorage from "@react-native-async-storage/async-storage"; +import { router, Slot, usePathname } from "expo-router"; +import { useEffect } from "react"; +import { Platform, View } from "react-native"; +import { create } from "zustand"; +import { createJSONStorage, persist } from "zustand/middleware"; + +import { cn } from "@turbostarter/ui"; +import { Button } from "@turbostarter/ui-mobile/button"; +import { Icons } from "@turbostarter/ui-mobile/icons"; + +import { pathsConfig } from "~/config/paths"; +import { SafeAreaView } from "~/modules/common/styled"; + +const steps = [ + pathsConfig.setup.steps.start, + pathsConfig.setup.steps.required, + pathsConfig.setup.steps.skip, + pathsConfig.setup.steps.final, +] as const; + +const useSetupStepsStore = create<{ + current: number; + setCurrent: (current: number) => void; +}>()( + persist( + (set) => ({ + current: 0, + setCurrent: (current) => set({ current }), + }), + { + name: "setup-steps", + storage: createJSONStorage(() => AsyncStorage), + }, + ), +); + +export const useSetupSteps = () => { + const pathname = usePathname(); + const { current, setCurrent } = useSetupStepsStore(); + + const step = steps[current]; + + useEffect(() => { + const index = steps.findIndex((step) => pathname.startsWith(step)); + + if (index != -1) { + setCurrent(index); + } + }, [pathname, setCurrent]); + + const goNext = () => { + const next = steps[current + 1]; + + if (!next) { + setCurrent(-1); + return; + } + router.navigate(next); + }; + + const goBack = () => { + const previous = steps[current - 1]; + + if (!previous) { + setCurrent(-1); + return; + } + + router.navigate(previous); + }; + + const reset = () => { + setCurrent(0); + }; + + return { + current, + steps, + step, + goNext, + goBack, + setCurrent, + reset, + }; +}; + +export default function StepsLayout() { + const { current, goBack, reset } = useSetupSteps(); + + return ( + + + + + + + {steps.map((_, index) => ( + + ))} + + + + + + + + ); +} diff --git a/apps/mobile/src/app/(setup)/steps/final.tsx b/apps/mobile/src/app/(setup)/steps/final.tsx new file mode 100644 index 0000000..301635f --- /dev/null +++ b/apps/mobile/src/app/(setup)/steps/final.tsx @@ -0,0 +1,45 @@ +import { router } from "expo-router"; +import { View } from "react-native"; + +import { useTranslation } from "@turbostarter/i18n"; +import { Button } from "@turbostarter/ui-mobile/button"; +import { Text } from "@turbostarter/ui-mobile/text"; + +import { useSetupSteps } from "~/app/(setup)/steps/_layout"; +import { pathsConfig } from "~/config/paths"; +import { ScrollView } from "~/modules/common/styled"; + +export default function FinalStep() { + const { t } = useTranslation(["common", "marketing"]); + const { goNext } = useSetupSteps(); + + return ( + <> + + + + {t("setup.steps.step.final.title")} + + + {t("setup.steps.step.final.description")} + + + + + + + ); +} diff --git a/apps/mobile/src/app/(setup)/steps/required.tsx b/apps/mobile/src/app/(setup)/steps/required.tsx new file mode 100644 index 0000000..ae25d02 --- /dev/null +++ b/apps/mobile/src/app/(setup)/steps/required.tsx @@ -0,0 +1,113 @@ +import { standardSchemaResolver } from "@hookform/resolvers/standard-schema"; +import * as Linking from "expo-linking"; +import { useForm } from "react-hook-form"; +import { View } from "react-native"; +import z from "zod"; + +import { Trans, useTranslation } from "@turbostarter/i18n"; +import { Button } from "@turbostarter/ui-mobile/button"; +import { Form, FormCheckbox, FormField } from "@turbostarter/ui-mobile/form"; +import { Text } from "@turbostarter/ui-mobile/text"; + +import { useSetupSteps } from "~/app/(setup)/steps/_layout"; +import { appConfig } from "~/config/app"; +import { ScrollView } from "~/modules/common/styled"; + +export default function RequiredStep() { + const { t } = useTranslation(["common", "marketing"]); + const { goNext } = useSetupSteps(); + + const form = useForm({ + resolver: standardSchemaResolver( + z.object({ + data: z.boolean(), + privacy: z.boolean(), + }), + ), + defaultValues: { + data: false, + privacy: false, + }, + }); + + const values = form.watch(); + + return ( + <> + + + + + {t("setup.steps.step.required.title")} + + + {t("setup.steps.step.required.description")} + + + + + + ( + + )} + /> + + ( + + Linking.openURL( + `${appConfig.url}/legal/privacy-policy`, + ) + } + className="font-sans-medium text-primary text-sm underline hover:no-underline" + /> + ), + }} + /> + } + value={!!field.value} + onChange={field.onChange} + onBlur={field.onBlur} + /> + )} + /> + + + + + + + + ); +} diff --git a/apps/mobile/src/app/(setup)/steps/skip.tsx b/apps/mobile/src/app/(setup)/steps/skip.tsx new file mode 100644 index 0000000..d0fafb8 --- /dev/null +++ b/apps/mobile/src/app/(setup)/steps/skip.tsx @@ -0,0 +1,40 @@ +import { View } from "react-native"; + +import { useTranslation } from "@turbostarter/i18n"; +import { Button } from "@turbostarter/ui-mobile/button"; +import { Text } from "@turbostarter/ui-mobile/text"; + +import { useSetupSteps } from "~/app/(setup)/steps/_layout"; +import { ScrollView } from "~/modules/common/styled"; + +export default function SkipStep() { + const { t } = useTranslation(["common", "marketing"]); + const { goNext } = useSetupSteps(); + + return ( + <> + + + + {t("setup.steps.step.skip.title")} + + + {t("setup.steps.step.skip.description")} + + + + + + + + + ); +} diff --git a/apps/mobile/src/app/(setup)/steps/start.tsx b/apps/mobile/src/app/(setup)/steps/start.tsx new file mode 100644 index 0000000..949fb36 --- /dev/null +++ b/apps/mobile/src/app/(setup)/steps/start.tsx @@ -0,0 +1,36 @@ +import { View } from "react-native"; + +import { useTranslation } from "@turbostarter/i18n"; +import { Button } from "@turbostarter/ui-mobile/button"; +import { Text } from "@turbostarter/ui-mobile/text"; + +import { useSetupSteps } from "~/app/(setup)/steps/_layout"; +import { ScrollView } from "~/modules/common/styled"; + +export default function StartStep() { + const { t } = useTranslation(["common", "marketing"]); + const { goNext } = useSetupSteps(); + + return ( + <> + + + + {t("setup.steps.step.start.title")} + + + {t("setup.steps.step.start.description")} + + + + + + + ); +} diff --git a/apps/mobile/src/app/(setup)/welcome.tsx b/apps/mobile/src/app/(setup)/welcome.tsx new file mode 100644 index 0000000..84fd9b9 --- /dev/null +++ b/apps/mobile/src/app/(setup)/welcome.tsx @@ -0,0 +1,107 @@ +/* eslint-disable @typescript-eslint/no-require-imports */ +import { Image } from "expo-image"; +import { router } from "expo-router"; +import { View } from "react-native"; + +import { useTranslation } from "@turbostarter/i18n"; +import { Button } from "@turbostarter/ui-mobile/button"; +import { + Slider, + SliderList, + SliderListItem, + SliderPaginationDots, + SliderPaginationDot, +} from "@turbostarter/ui-mobile/slider"; +import { Text } from "@turbostarter/ui-mobile/text"; + +import { pathsConfig } from "~/config/paths"; +import { useTheme } from "~/modules/common/hooks/use-theme"; +import { SafeAreaView } from "~/modules/common/styled"; +import { WIDTH } from "~/utils/device"; + +import type { ImageSource } from "expo-image"; + +const images = [ + { + light: require("../../../public/images/setup/1/light.png") as ImageSource, + dark: require("../../../public/images/setup/1/dark.png") as ImageSource, + }, + { + light: require("../../../public/images/setup/2/light.png") as ImageSource, + dark: require("../../../public/images/setup/2/dark.png") as ImageSource, + }, + { + light: require("../../../public/images/setup/3/light.png") as ImageSource, + dark: require("../../../public/images/setup/3/dark.png") as ImageSource, + }, +]; + +const ITEM_WIDTH = WIDTH - 48; + +const WelcomePage = () => { + const { resolvedTheme } = useTheme(); + const { t } = useTranslation(["common", "marketing", "auth"]); + + return ( + + + + + ( + + + + )} + /> + + {images.map((_, index) => ( + + ))} + + + + + + {t("product.title")} + + + + {t("product.description")} + + + + + + + + + + + + ); +}; + +export default WelcomePage; diff --git a/apps/mobile/src/app/+not-found.tsx b/apps/mobile/src/app/+not-found.tsx new file mode 100644 index 0000000..ebd44d3 --- /dev/null +++ b/apps/mobile/src/app/+not-found.tsx @@ -0,0 +1,32 @@ +import { router } from "expo-router"; +import { View } from "react-native"; + +import { useTranslation } from "@turbostarter/i18n"; +import { Button } from "@turbostarter/ui-mobile/button"; +import { Text } from "@turbostarter/ui-mobile/text"; + +import { pathsConfig } from "~/config/paths"; + +export default function NotFound() { + const { t } = useTranslation("common"); + + return ( + + + + {t("error.notFound")} + + + {t("error.resourceDoesNotExist")} + + + + + + ); +} diff --git a/apps/mobile/src/app/_layout.tsx b/apps/mobile/src/app/_layout.tsx new file mode 100644 index 0000000..e5695c3 --- /dev/null +++ b/apps/mobile/src/app/_layout.tsx @@ -0,0 +1,122 @@ +import { useReactNavigationDevTools } from "@dev-plugins/react-navigation"; +import { + Geist_400Regular, + Geist_500Medium, + Geist_600SemiBold, + Geist_700Bold, + useFonts, +} from "@expo-google-fonts/geist"; +import { GeistMono_400Regular } from "@expo-google-fonts/geist-mono"; +import { router, Stack, useNavigationContainerRef } from "expo-router"; +import * as SplashScreen from "expo-splash-screen"; +import { View } from "react-native"; + +import { useTranslation } from "@turbostarter/i18n"; +import { Button } from "@turbostarter/ui-mobile/button"; +import { Text } from "@turbostarter/ui-mobile/text"; + +import "~/assets/styles/globals.css"; +import { pathsConfig } from "~/config/paths"; +import { authClient } from "~/lib/auth"; +import "~/lib/polyfills"; +import { Providers } from "~/lib/providers/providers"; +import { useTheme } from "~/modules/common/hooks/use-theme"; +import { Updates } from "~/modules/common/updates"; + +import type { ErrorBoundaryProps } from "expo-router"; + +void SplashScreen.preventAutoHideAsync(); + +SplashScreen.setOptions({ + duration: 500, + fade: true, +}); + +const RootNavigator = () => { + const navigationRef = useNavigationContainerRef(); + useReactNavigationDevTools( + navigationRef as Parameters[0], + ); + + return ( + + + + + ); +}; + +const useSetupAuth = () => { + const session = authClient.useSession(); + const activeOrganization = authClient.useActiveOrganization(); + const activeMember = authClient.useActiveMember(); + + if (session.isPending) { + return false; + } + + if (!session.data) { + return true; + } + + return !activeOrganization.isPending && !activeMember.isPending; +}; + +const RootLayout = () => { + useTheme(); + const [fontsLoaded] = useFonts({ + GeistMono_400Regular, + Geist_400Regular, + Geist_500Medium, + Geist_600SemiBold, + Geist_700Bold, + }); + + const authLoaded = useSetupAuth(); + + const loaded = fontsLoaded && authLoaded; + + if (loaded) { + SplashScreen.hide(); + } + + return ; +}; + +export default RootLayout; + +export function ErrorBoundary({ error, retry }: ErrorBoundaryProps) { + const { t } = useTranslation("common"); + + return ( + + + + {t("error.general")} + + + {error.message || t("error.apologies")} + + + + + + + + + + ); +} diff --git a/apps/mobile/src/app/dashboard/(user)/_layout.tsx b/apps/mobile/src/app/dashboard/(user)/_layout.tsx new file mode 100644 index 0000000..eb62f32 --- /dev/null +++ b/apps/mobile/src/app/dashboard/(user)/_layout.tsx @@ -0,0 +1,81 @@ +import { Tabs } from "expo-router"; +import { Easing } from "react-native"; + +import { useTranslation } from "@turbostarter/i18n"; +import { cn } from "@turbostarter/ui"; +import { Icons } from "@turbostarter/ui-mobile/icons"; + +import { UserHeader } from "~/modules/common/layout/header"; +import { TabBarLabel } from "~/modules/common/styled"; + +export default function UserLayout() { + const { t } = useTranslation("common"); + + return ( + + , + title: t("home"), + tabBarIcon: ({ focused }) => ( + + ), + tabBarLabel: TabBarLabel, + }} + /> + ( + + ), + tabBarLabel: TabBarLabel, + }} + /> + ( + + ), + tabBarLabel: TabBarLabel, + }} + /> + + ); +} diff --git a/apps/mobile/src/app/dashboard/(user)/ai.tsx b/apps/mobile/src/app/dashboard/(user)/ai.tsx new file mode 100644 index 0000000..f290913 --- /dev/null +++ b/apps/mobile/src/app/dashboard/(user)/ai.tsx @@ -0,0 +1,176 @@ +import { useChat } from "@ai-sdk/react"; +import { DefaultChatTransport } from "ai"; +import { fetch as expoFetch } from "expo/fetch"; +import { useState } from "react"; +import { View, Keyboard } from "react-native"; +import { FlatList } from "react-native-gesture-handler"; +import Markdown from "react-native-marked"; +import { useSafeAreaInsets } from "react-native-safe-area-context"; + +import { useTranslation } from "@turbostarter/i18n"; +import { logger } from "@turbostarter/shared/logger"; +import { cn } from "@turbostarter/ui"; +import { Button } from "@turbostarter/ui-mobile/button"; +import { Icons } from "@turbostarter/ui-mobile/icons"; +import { Spin } from "@turbostarter/ui-mobile/spin"; +import { Text } from "@turbostarter/ui-mobile/text"; +import { Textarea } from "@turbostarter/ui-mobile/textarea"; + +import { api } from "~/lib/api"; +import { KeyboardAvoidingView, ScrollView } from "~/modules/common/styled"; + +const EXAMPLES = [ + { + icon: Icons.Globe2, + prompt: "ai.prompt.history", + }, + { + icon: Icons.GraduationCap, + prompt: "ai.prompt.capitals", + }, + { + icon: Icons.Atom, + prompt: "ai.prompt.quantum", + }, + { + icon: Icons.Brain, + prompt: "ai.prompt.realWorld", + }, +] as const; + +export default function AI() { + const { t } = useTranslation("marketing"); + const [input, setInput] = useState(""); + const insets = useSafeAreaInsets(); + + const { messages, error, sendMessage, status } = useChat({ + transport: new DefaultChatTransport({ + fetch: expoFetch as unknown as typeof globalThis.fetch, + api: api.ai.chat.chats.$url().toString(), + }), + onError: (error) => logger.error(error), + }); + + if (error) { + return ( + + {error.message} + + ); + } + + const messagesToDisplay = messages.filter((message) => + ["assistant", "user"].includes(message.role), + ); + + const isLoading = ["submitted", "streaming"].includes(status); + + return ( + + + {messagesToDisplay.map((message) => ( + + {message.parts.map((part, i) => { + switch (part.type) { + case "text": + return message.role === "assistant" ? ( + + ) : ( + {part.text} + ); + } + })} + + ))} + {isLoading && ( + + + + + + )} + + + {!messagesToDisplay.length && ( + ( + + )} + /> + )} + + +